#include "postgres.h"
#include "pgstat.h"
+#include "access/transam.h"
+#include "access/xact.h"
#include "catalog/namespace.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
#endif
static bool online_analyze_enable = true;
+static bool online_analyze_local_tracking = false;
static bool online_analyze_verbose = true;
static double online_analyze_scale_factor = 0.1;
static int online_analyze_threshold = 50;
CK_INSERT = CMD_INSERT,
CK_DELETE = CMD_DELETE,
CK_TRUNCATE,
- CK_CREATE
+ CK_FASTTRUNCATE,
+ CK_CREATE,
+ CK_ANALYZE,
+ CK_VACUUM
} CmdKind;
+
typedef enum
{
OATT_ALL = 0x03,
newTable = true;
}
- Assert(rstat->tableid == relOid);
+ if (operation == CK_VACUUM)
+ {
+ /* force reread because vacuum could change n_tuples */
+ rstat->rereadStat = true;
+ return;
+ }
+ else if (operation == CK_ANALYZE)
+ {
+ /* only analyze */
+ rstat->changes_since_analyze = 0;
+ rstat->analyze_timestamp = now;
+ if (newTable)
+ rstat->rereadStat = true;
+ return;
+ }
- elog(NOTICE,"makeAnalyze op:%d %u", operation, naffected);
+ Assert(rstat->tableid == relOid);
- if (operation != CK_TRUNCATE &&
- (found == false || rstat->rereadStat == true))
+ if (
+ /* do not reread data if it was a truncation */
+ operation != CK_TRUNCATE && operation != CK_FASTTRUNCATE &&
+ /* read for persistent table and for temp teble if it allowed */
+ (reltype == OATT_PERSISTENT || online_analyze_local_tracking == false) &&
+ /* read only for new table or we know that it's needed */
+ (newTable == true || rstat->rereadStat == true)
+ )
{
-
rstat->rereadStat = false;
tabentry = pgstat_fetch_stat_tabentry(relOid);
}
if (newTable ||
- /* force analyze from after truncate */
+ /* force analyze after truncate, fasttruncate already did analyze */
operation == CK_TRUNCATE || (
/* do not analyze too often, if both stamps are exceeded the go */
TimestampDifferenceExceeds(rstat->analyze_timestamp, now, online_analyze_min_interval) &&
#endif
);
+ /* Make changes visible to subsequent calls */
+ CommandCounterIncrement();
+
if (online_analyze_verbose)
{
long secs;
switch(operation)
{
+ case CK_CREATE:
case CK_INSERT:
- rstat->n_tuples += naffected;
- rstat->rereadStat = false;
- break;
case CK_UPDATE:
rstat->n_tuples += naffected;
- rstat->rereadStat = true;
- break;
case CK_DELETE:
- rstat->rereadStat = true;
+ rstat->rereadStat = (reltype == OATT_PERSISTENT);
break;
case CK_TRUNCATE:
- rstat->n_tuples = 0;
+ case CK_FASTTRUNCATE:
rstat->rereadStat = false;
+ rstat->n_tuples = 0;
break;
default:
break;
#endif
switch(operation)
{
+ case CK_CREATE:
case CK_INSERT:
rstat->changes_since_analyze += naffected;
rstat->n_tuples += naffected;
rstat->changes_since_analyze += naffected;
break;
case CK_TRUNCATE:
- rstat->changes_since_analyze = rstat->n_tuples;
+ case CK_FASTTRUNCATE:
+ rstat->changes_since_analyze = 0;
rstat->n_tuples = 0;
break;
default:
relstatsInit();
}
+static Const*
+isFastTruncateCall(QueryDesc *queryDesc)
+{
+ TargetEntry *te;
+ FuncExpr *fe;
+ Const *constval;
+
+ if (!(
+ queryDesc->plannedstmt &&
+ queryDesc->operation == CMD_SELECT &&
+ queryDesc->plannedstmt->planTree &&
+ queryDesc->plannedstmt->planTree->targetlist &&
+ list_length(queryDesc->plannedstmt->planTree->targetlist) == 1
+ ))
+ return NULL;
+
+ te = linitial(queryDesc->plannedstmt->planTree->targetlist);
+
+ if (!IsA(te, TargetEntry))
+ return NULL;
+
+ fe = (FuncExpr*)te->expr;
+
+ if (!(
+ fe && IsA(fe, FuncExpr) &&
+ fe->funcid >= FirstNormalObjectId &&
+ fe->funcretset == false &&
+ fe->funcresulttype == VOIDOID &&
+ fe->funcvariadic == false &&
+ list_length(fe->args) == 1
+ ))
+ return NULL;
+
+ constval = linitial(fe->args);
+
+ if (!(
+ IsA(constval,Const) &&
+ constval->consttype == TEXTOID &&
+ strcmp(get_func_name(fe->funcid), "fasttruncate") == 0
+ ))
+ return NULL;
+
+ return constval;
+}
+
+
extern PGDLLIMPORT void onlineAnalyzeHooker(QueryDesc *queryDesc);
void
onlineAnalyzeHooker(QueryDesc *queryDesc)
{
int64 naffected = -1;
+ Const *constval;
if (queryDesc->estate)
naffected = queryDesc->estate->es_processed;
+#if PG_VERSION_NUM >= 90200
+ if (online_analyze_enable &&
+ (constval = isFastTruncateCall(queryDesc)) != NULL)
+ {
+ Datum tblnamed = constval->constvalue;
+ char *tblname = text_to_cstring(DatumGetTextP(tblnamed));
+ RangeVar *tblvar =
+ makeRangeVarFromNameList(stringToQualifiedNameList(tblname));
+
+ makeAnalyze(RangeVarGetRelid(tblvar,
+ NoLock,
+ false),
+ CK_FASTTRUNCATE, -1);
+ }
+#endif
+
if (online_analyze_enable && queryDesc->plannedstmt &&
(queryDesc->operation == CMD_INSERT ||
queryDesc->operation == CMD_UPDATE ||
standard_ExecutorEnd(queryDesc);
}
+static List *toremove = NIL;
+
+/*
+ * removeTable called on transaction end, see call RegisterXactCallback() below
+ */
+static void
+removeTable(XactEvent event, void *arg)
+{
+ ListCell *cell;
+
+ switch(event)
+ {
+ case XACT_EVENT_COMMIT:
+ break;
+ case XACT_EVENT_ABORT:
+ toremove = NIL;
+ default:
+ return;
+ }
+
+ foreach(cell, toremove)
+ {
+ Oid relOid = lfirst_oid(cell);
+
+ hash_search(relstats, &relOid, HASH_REMOVE, NULL);
+ }
+
+ toremove = NIL;
+}
+
+
#if PG_VERSION_NUM >= 90200
static void
onlineAnalyzeHookerUtility(
tblnames = list_copy(((TruncateStmt*)parsetree)->relations);
op = CK_TRUNCATE;
}
+ else if (IsA(parsetree, DropStmt) &&
+ ((DropStmt*)parsetree)->removeType == OBJECT_TABLE)
+ {
+ ListCell *cell;
+
+ foreach(cell, ((DropStmt*)parsetree)->objects)
+ {
+ List *relname = (List *) lfirst(cell);
+ RangeVar *rel = makeRangeVarFromNameList(relname);
+ Oid relOid = RangeVarGetRelid(rel, NoLock, true);
+
+ if (OidIsValid(relOid))
+ {
+ MemoryContext ctx;
+
+ ctx = MemoryContextSwitchTo(TopTransactionContext);
+ toremove = lappend_oid(toremove, relOid);
+ MemoryContextSwitchTo(ctx);
+ }
+ }
+ }
+ else if (IsA(parsetree, VacuumStmt))
+ {
+ VacuumStmt *vac = (VacuumStmt*)parsetree;
+
+#if PG_VERSION_NUM >= 110000
+ tblnames = vac->rels;
+#else
+ if (vac->relation)
+ tblnames = list_make1(vac->relation);
+#endif
+
+ if (vac->options & (VACOPT_VACUUM | VACOPT_FULL | VACOPT_FREEZE))
+ {
+ /* optionally with analyze */
+ op = CK_VACUUM;
+
+ /* drop all collected stat */
+ if (tblnames == NIL)
+ relstatsInit();
+ }
+ else if (vac->options & VACOPT_ANALYZE)
+ {
+ op = CK_ANALYZE;
+
+ /* should reset all counters */
+ if (tblnames == NIL)
+ {
+ HASH_SEQ_STATUS hs;
+ OnlineAnalyzeTableStat *rstat;
+ TimestampTz now = GetCurrentTimestamp();
+
+ hash_seq_init(&hs, relstats);
+
+ while((rstat = hash_seq_search(&hs)) != NULL)
+ {
+ rstat->changes_since_analyze = 0;
+ rstat->analyze_timestamp = now;
+ }
+ }
+ }
+ else
+ tblnames = NIL;
+ }
}
#if PG_VERSION_NUM >= 100000
foreach(l, tblnames)
{
- RangeVar *tblname = (RangeVar*)lfirst(l);
- Oid tblOid = RangeVarGetRelid(tblname, NoLock, true);
+ RangeVar *tblname =
+#if PG_VERSION_NUM >= 110000
+ (IsA(lfirst(l), VacuumRelation)) ?
+ ((VacuumRelation*)lfirst(l))->relation :
+#endif
+ (RangeVar*)lfirst(l);
+ Oid tblOid;
+
+ Assert(IsA(tblname, RangeVar));
+ tblOid = RangeVarGetRelid(tblname, NoLock, true);
makeAnalyze(tblOid, op, -1);
}
}
NULL
);
+ DefineCustomBoolVariable(
+ "online_analyze.local_tracking",
+ "Per backend tracking",
+ "Per backend tracking for temp tables (do not use system statistic)",
+ &online_analyze_local_tracking,
+#if PG_VERSION_NUM >= 80400
+ online_analyze_local_tracking,
+#endif
+ PGC_USERSET,
+#if PG_VERSION_NUM >= 80400
+ GUC_NOT_IN_SAMPLE,
+#if PG_VERSION_NUM >= 90100
+ NULL,
+#endif
+#endif
+ NULL,
+ NULL
+ );
+
DefineCustomBoolVariable(
"online_analyze.verbose",
"Verbosity of on-line analyze",
NULL
);
+ RegisterXactCallback(removeTable, NULL);
}
void _PG_fini(void);