Merge branch 'n_tuples_limit' of ssh://hook.sigaev.ru/spool/git/online_analyze into...
[online_analyze.git] / online_analyze.c
index 93276ab..2ef4193 100644 (file)
@@ -30,6 +30,7 @@
 #include "postgres.h"
 
 #include "pgstat.h"
+#include "access/transam.h"
 #include "catalog/namespace.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
 #include "utils/timestamp.h"
 #if PG_VERSION_NUM >= 90500
 #include "nodes/makefuncs.h"
+#if PG_VERSION_NUM >= 100000
+#include "utils/varlena.h"
+#include "utils/regproc.h"
+#endif
 #endif
 #endif
 
@@ -63,12 +68,23 @@ static double online_analyze_scale_factor = 0.1;
 static int online_analyze_threshold = 50;
 static int online_analyze_capacity_threshold = 100000;
 static double online_analyze_min_interval = 10000;
+static int online_analyze_lower_limit = 0;
 
 static ExecutorEnd_hook_type oldExecutorEndHook = NULL;
 #if PG_VERSION_NUM >= 90200
 static ProcessUtility_hook_type        oldProcessUtilityHook = NULL;
 #endif
 
+typedef enum CmdKind
+{
+       CK_SELECT = CMD_SELECT,
+       CK_UPDATE = CMD_UPDATE,
+       CK_INSERT = CMD_INSERT,
+       CK_DELETE = CMD_DELETE,
+       CK_TRUNCATE,
+       CK_CREATE
+} CmdKind;
+
 typedef enum
 {
        OATT_ALL                = 0x03,
@@ -109,8 +125,9 @@ typedef struct OnlineAnalyzeTableStat {
 static MemoryContext   onlineAnalyzeMemoryContext = NULL;
 static HTAB    *relstats = NULL;
 
-static void relstatsInit();
+static void relstatsInit(void);
 
+#if PG_VERSION_NUM < 100000
 static int
 oid_cmp(const void *a, const void *b)
 {
@@ -118,6 +135,7 @@ oid_cmp(const void *a, const void *b)
                return 0;
        return (*(Oid*)a > *(Oid*)b) ? 1 : -1;
 }
+#endif
 
 static const char *
 tableListAssign(const char * newval, bool doit, TableList *tbl)
@@ -336,7 +354,7 @@ makeRangeVarFromOid(Oid relOid)
 #endif
 
 static void
-makeAnalyze(Oid relOid, CmdType operation, int32 naffected)
+makeAnalyze(Oid relOid, CmdKind operation, int64 naffected)
 {
        TimestampTz                             now = GetCurrentTimestamp();
        Relation                                rel;
@@ -416,15 +434,22 @@ makeAnalyze(Oid relOid, CmdType operation, int32 naffected)
        else
                rstat = &dummyrstat; /* found == false for following if */
 
-       if (found == false || rstat->rereadStat == true || naffected == 0)
+       if (!found)
        {
+               MemSet(rstat, 0, sizeof(*rstat));
+               rstat->tableid = relOid;
+               newTable = true;
+       }
 
-               if (!found)
-               {
-                       MemSet(rstat, 0, sizeof(*rstat));
-                       rstat->tableid = relOid;
-               }
-               Assert(rstat->tableid == relOid);
+       Assert(rstat->tableid == relOid);
+
+       elog(NOTICE,"makeAnalyze op:%d %u", operation, naffected);
+
+       if (operation != CK_TRUNCATE &&
+               (found == false || rstat->rereadStat == true))
+       {
+
+               rstat->rereadStat = false;
 
                tabentry = pgstat_fetch_stat_tabentry(relOid);
 
@@ -441,19 +466,17 @@ makeAnalyze(Oid relOid, CmdType operation, int32 naffected)
                        rstat->autovac_analyze_timestamp =
                                tabentry->autovac_analyze_timestamp;
                        rstat->analyze_timestamp = tabentry->analyze_timestamp;
-                       rstat->rereadStat = false;
-               }
-               else
-               {
-                       newTable = true;
-                       rstat->rereadStat = true;
                }
        }
 
-       if (newTable || (
+       if (newTable ||
+               /* force analyze from after truncate */
+               operation == CK_TRUNCATE || (
                /* do not analyze too often, if both stamps are exceeded the go */
                TimestampDifferenceExceeds(rstat->analyze_timestamp, now, online_analyze_min_interval) &&
                TimestampDifferenceExceeds(rstat->autovac_analyze_timestamp, now, online_analyze_min_interval) &&
+               /* do not analyze too small tables */
+               rstat->n_tuples + rstat->changes_since_analyze + naffected > online_analyze_lower_limit &&
                /* be in sync with relation_needs_vacanalyze */
                ((double)(rstat->changes_since_analyze + naffected)) >=
                         online_analyze_scale_factor * ((double)rstat->n_tuples) +
@@ -527,7 +550,27 @@ makeAnalyze(Oid relOid, CmdType operation, int32 naffected)
 
                rstat->autovac_analyze_timestamp = now;
                rstat->changes_since_analyze = 0;
-               rstat->rereadStat = true;
+
+               switch(operation)
+               {
+                       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;
+                               break;
+                       case CK_TRUNCATE:
+                               rstat->n_tuples = 0;
+                               rstat->rereadStat = false;
+                               break;
+                       default:
+                               break;
+               }
 
                /* update last analyze timestamp in local memory of backend */
                if (tabentry)
@@ -547,7 +590,25 @@ makeAnalyze(Oid relOid, CmdType operation, int32 naffected)
                if (tabentry)
                        tabentry->changes_since_analyze += naffected;
 #endif
-               rstat->changes_since_analyze += naffected;
+               switch(operation)
+               {
+                       case CK_INSERT:
+                               rstat->changes_since_analyze += naffected;
+                               rstat->n_tuples += naffected;
+                               break;
+                       case CK_UPDATE:
+                               rstat->changes_since_analyze += 2 * naffected;
+                               rstat->n_tuples += naffected;
+                       case CK_DELETE:
+                               rstat->changes_since_analyze += naffected;
+                               break;
+                       case CK_TRUNCATE:
+                               rstat->changes_since_analyze = 0;
+                               rstat->n_tuples = 0;
+                               break;
+                       default:
+                               break;
+               }
        }
 
        /* Reset local cache if we are over limit */
@@ -555,15 +616,81 @@ makeAnalyze(Oid relOid, CmdType operation, int32 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 &&
+                 IsA(linitial(queryDesc->plannedstmt->planTree->targetlist), TargetEntry)
+                ))
+               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 &&
+                 IsA(linitial(fe->args), Const)
+                ))
+               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)
 {
-       uint32  naffected = -1;
+       int64   naffected = -1;
+       Const   *constval;
 
        if (queryDesc->estate)
                naffected = queryDesc->estate->es_processed;
 
+       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,
+#if PG_VERSION_NUM >= 90200
+                                                                        NoLock,
+#endif
+                                                                        false),
+                                       CK_TRUNCATE, -1);
+       }
+
        if (online_analyze_enable && queryDesc->plannedstmt &&
                        (queryDesc->operation == CMD_INSERT ||
                         queryDesc->operation == CMD_UPDATE ||
@@ -594,7 +721,7 @@ onlineAnalyzeHooker(QueryDesc *queryDesc)
                                RangeTblEntry   *rte = list_nth(queryDesc->plannedstmt->rtable, n-1);
 
                                if (rte->rtekind == RTE_RELATION)
-                                       makeAnalyze(rte->relid, queryDesc->operation, naffected);
+                                       makeAnalyze(rte->relid, (CmdKind)queryDesc->operation, naffected);
                        }
                }
        }
@@ -607,22 +734,58 @@ onlineAnalyzeHooker(QueryDesc *queryDesc)
 
 #if PG_VERSION_NUM >= 90200
 static void
-onlineAnalyzeHookerUtility(Node *parsetree, const char *queryString,
+onlineAnalyzeHookerUtility(
+#if PG_VERSION_NUM >= 100000
+                                                  PlannedStmt *pstmt,
+#else
+                                                  Node *parsetree,
+#endif
+                                                  const char *queryString,
 #if PG_VERSION_NUM >= 90300
                                                        ProcessUtilityContext context, ParamListInfo params,
+#if PG_VERSION_NUM >= 100000
+                                                       QueryEnvironment *queryEnv,
+#endif
 #else
                                                        ParamListInfo params, bool isTopLevel,
 #endif
                                                        DestReceiver *dest, char *completionTag) {
-       RangeVar        *tblname = NULL;
+       List            *tblnames = NIL;
+       CmdKind         op = CK_INSERT;
+#if PG_VERSION_NUM >= 100000
+       Node            *parsetree = NULL;
 
-       if (IsA(parsetree, CreateTableAsStmt) && ((CreateTableAsStmt*)parsetree)->into)
-               tblname = (RangeVar*)copyObject(((CreateTableAsStmt*)parsetree)->into->rel);
+       if (pstmt->commandType == CMD_UTILITY)
+               parsetree = pstmt->utilityStmt;
+#endif
+
+       if (parsetree && online_analyze_enable)
+       {
+               if (IsA(parsetree, CreateTableAsStmt) &&
+                       ((CreateTableAsStmt*)parsetree)->into)
+               {
+                       tblnames =
+                               list_make1((RangeVar*)copyObject(((CreateTableAsStmt*)parsetree)->into->rel));
+                       op = CK_CREATE;
+               }
+               else if (IsA(parsetree, TruncateStmt))
+               {
+                       tblnames = list_copy(((TruncateStmt*)parsetree)->relations);
+                       op = CK_TRUNCATE;
+               }
+       }
+
+#if PG_VERSION_NUM >= 100000
+#define parsetree pstmt
+#endif
 
        if (oldProcessUtilityHook)
                oldProcessUtilityHook(parsetree, queryString,
 #if PG_VERSION_NUM >= 90300
                                                          context, params,
+#if PG_VERSION_NUM >= 100000
+                                                         queryEnv,
+#endif
 #else
                                                          params, isTopLevel,
 #endif
@@ -631,21 +794,34 @@ onlineAnalyzeHookerUtility(Node *parsetree, const char *queryString,
                standard_ProcessUtility(parsetree, queryString,
 #if PG_VERSION_NUM >= 90300
                                                                context, params,
+#if PG_VERSION_NUM >= 100000
+                                                               queryEnv,
+#endif
 #else
                                                                params, isTopLevel,
 #endif
                                                                dest, completionTag);
 
-       if (tblname) {
-               Oid     tblOid = RangeVarGetRelid(tblname, NoLock, true);
+#if PG_VERSION_NUM >= 100000
+#undef parsetree
+#endif
+
+       if (tblnames) {
+               ListCell        *l;
+
+               foreach(l, tblnames)
+               {
+                       RangeVar        *tblname = (RangeVar*)lfirst(l);
+                       Oid     tblOid = RangeVarGetRelid(tblname, NoLock, true);
 
-               makeAnalyze(tblOid, CMD_INSERT, -1);
+                       makeAnalyze(tblOid, op, -1);
+               }
        }
 }
 #endif
 
 static void
-relstatsInit()
+relstatsInit(void)
 {
        HASHCTL hash_ctl;
        int             flags = 0;
@@ -666,12 +842,17 @@ relstatsInit()
                onlineAnalyzeMemoryContext =
                        AllocSetContextCreate(CacheMemoryContext,
                                                                  "online_analyze storage context",
-                                                                 ALLOCSET_DEFAULT_SIZES);
+#if PG_VERSION_NUM < 90600
+                                                                 ALLOCSET_DEFAULT_MINSIZE,
+                                                                 ALLOCSET_DEFAULT_INITSIZE,
+                                                                 ALLOCSET_DEFAULT_MAXSIZE
+#else
+                                                                 ALLOCSET_DEFAULT_SIZES
+#endif
+                                                                );
        }
 
-       hash_ctl.hcxt = AllocSetContextCreate(CacheMemoryContext,
-                                                                                 "online_analyze storage context",
-                                                                                 ALLOCSET_DEFAULT_SIZES);
+       hash_ctl.hcxt = onlineAnalyzeMemoryContext;
        flags |= HASH_CONTEXT;
 
        hash_ctl.keysize = sizeof(Oid);
@@ -878,6 +1059,28 @@ _PG_init(void)
 #endif
                includeTablesShow
        );
+
+       DefineCustomIntVariable(
+               "online_analyze.lower_limit",
+               "min number of rows in table to analyze",
+               "min number of rows in table to analyze",
+               &online_analyze_lower_limit,
+#if PG_VERSION_NUM >= 80400
+               online_analyze_lower_limit,
+#endif
+               0,
+               0x7fffffff,
+               PGC_USERSET,
+#if PG_VERSION_NUM >= 80400
+               GUC_NOT_IN_SAMPLE,
+#if PG_VERSION_NUM >= 90100
+               NULL,
+#endif
+#endif
+               NULL,
+               NULL
+       );
+
 }
 
 void _PG_fini(void);