fix forcing online analyze in case of analyze command
[online_analyze.git] / online_analyze.c
index dba4226..4396550 100644 (file)
@@ -30,6 +30,8 @@
 #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"
@@ -62,6 +64,7 @@ PG_MODULE_MAGIC;
 #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;
@@ -81,9 +84,13 @@ typedef enum CmdKind
        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,
@@ -440,14 +447,33 @@ makeAnalyze(Oid relOid, CmdKind operation, int64 naffected)
                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);
@@ -469,7 +495,7 @@ makeAnalyze(Oid relOid, CmdKind operation, int64 naffected)
        }
 
        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) &&
@@ -535,6 +561,9 @@ makeAnalyze(Oid relOid, CmdKind operation, int64 naffected)
 #endif
                );
 
+               /* Make changes visible to subsequent calls */
+               CommandCounterIncrement();
+
                if (online_analyze_verbose)
                {
                        long    secs;
@@ -552,20 +581,17 @@ makeAnalyze(Oid relOid, CmdKind operation, int64 naffected)
 
                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;
@@ -591,6 +617,7 @@ makeAnalyze(Oid relOid, CmdKind operation, int64 naffected)
 #endif
                switch(operation)
                {
+                       case CK_CREATE:
                        case CK_INSERT:
                                rstat->changes_since_analyze += naffected;
                                rstat->n_tuples += naffected;
@@ -602,7 +629,8 @@ makeAnalyze(Oid relOid, CmdKind operation, int64 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:
@@ -615,15 +643,78 @@ makeAnalyze(Oid relOid, CmdKind operation, int64 naffected)
                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 ||
@@ -665,6 +756,37 @@ onlineAnalyzeHooker(QueryDesc *queryDesc)
                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(
@@ -706,6 +828,70 @@ 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
@@ -744,9 +930,17 @@ onlineAnalyzeHookerUtility(
 
                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);
                }
        }
@@ -832,6 +1026,25 @@ _PG_init(void)
                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",
@@ -1014,6 +1227,7 @@ _PG_init(void)
                NULL
        );
 
+       RegisterXactCallback(removeTable, NULL);
 }
 
 void _PG_fini(void);