X-Git-Url: http://sigaev.ru/git/gitweb.cgi?a=blobdiff_plain;f=online_analyze.c;h=afc6b296277d5ebe36e89dd45a093de60bd92cb1;hb=HEAD;hp=a823216a011c3755a9ec1c31f0b9c6e594e96a3f;hpb=14e9a758e05dd5467bab85ef98fad76b8dbfcc87;p=online_analyze.git diff --git a/online_analyze.c b/online_analyze.c index a823216..0021fc3 100644 --- a/online_analyze.c +++ b/online_analyze.c @@ -6,13 +6,13 @@ * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. + * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of any co-contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. + * may be used to endorse or promote products derived from this software + * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -30,6 +30,9 @@ #include "postgres.h" #include "pgstat.h" +#include "miscadmin.h" +#include "access/transam.h" +#include "access/xact.h" #include "catalog/namespace.h" #include "commands/vacuum.h" #include "executor/executor.h" @@ -37,13 +40,27 @@ #include "nodes/parsenodes.h" #include "storage/bufmgr.h" #include "utils/builtins.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" #include "utils/lsyscache.h" #include "utils/guc.h" #if PG_VERSION_NUM >= 90200 #include "catalog/pg_class.h" +#include "nodes/primnodes.h" +#include "tcop/utility.h" #include "utils/rel.h" #include "utils/relcache.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" +#if PG_VERSION_NUM >= 130000 +#include "common/hashfn.h" +#endif +#endif +#endif #endif #ifdef PG_MODULE_MAGIC @@ -51,12 +68,38 @@ 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; +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 +#if PG_VERSION_NUM >= 120000 +#define VACOPT_NOWAIT VACOPT_SKIP_LOCKED +#endif + +typedef enum CmdKind +{ + CK_SELECT = CMD_SELECT, + CK_UPDATE = CMD_UPDATE, + CK_INSERT = CMD_INSERT, + CK_DELETE = CMD_DELETE, + CK_TRUNCATE, + CK_FASTTRUNCATE, + CK_CREATE, + CK_ANALYZE, + CK_VACUUM +} CmdKind; + + +typedef enum { OATT_ALL = 0x03, OATT_PERSISTENT = 0x01, @@ -64,7 +107,7 @@ typedef enum OATT_NONE = 0x00 } OnlineAnalyzeTableType; -static const struct config_enum_entry online_analyze_table_type_options[] = +static const struct config_enum_entry online_analyze_table_type_options[] = { {"all", OATT_ALL, false}, {"persistent", OATT_PERSISTENT, false}, @@ -79,11 +122,27 @@ typedef struct TableList { int nTables; Oid *tables; char *tableStr; + bool inited; } TableList; -static TableList excludeTables = {0, NULL, NULL}; -static TableList includeTables = {0, NULL, NULL}; +static TableList excludeTables = {0, NULL, NULL, false}; +static TableList includeTables = {0, NULL, NULL, false}; + +typedef struct OnlineAnalyzeTableStat { + Oid tableid; + bool rereadStat; + PgStat_Counter n_tuples; + PgStat_Counter mod_since_analyze; + TimestampTz last_autoanalyze_time; + TimestampTz last_analyze_time; +} OnlineAnalyzeTableStat; + +static MemoryContext onlineAnalyzeMemoryContext = NULL; +static HTAB *relstats = NULL; +static void relstatsInit(void); + +#if PG_VERSION_NUM < 100000 static int oid_cmp(const void *a, const void *b) { @@ -91,15 +150,16 @@ 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) { - char *rawname; - List *namelist; - ListCell *l; - Oid *newOids = NULL; - int nOids = 0, + char *rawname; + List *namelist; + ListCell *l; + Oid *newOids = NULL; + int nOids = 0, i = 0; rawname = pstrdup(newval); @@ -107,22 +167,45 @@ tableListAssign(const char * newval, bool doit, TableList *tbl) if (!SplitIdentifierString(rawname, ',', &namelist)) goto cleanup; + /* + * follow work could be done only in normal processing because of + * accsess to system catalog + */ +#if PG_VERSION_NUM >= 170000 + if (MyProcNumber == INVALID_PROC_NUMBER || +#else + if (MyBackendId == InvalidBackendId || +#endif + !IsUnderPostmaster || + !IsTransactionState()) + { + includeTables.inited = false; + excludeTables.inited = false; + return newval; + } + if (doit) { nOids = list_length(namelist); newOids = malloc(sizeof(Oid) * (nOids+1)); if (!newOids) - elog(ERROR,"could not allocate %d bytes", (int)(sizeof(Oid) * (nOids+1))); + elog(ERROR,"could not allocate %d bytes", + (int)(sizeof(Oid) * (nOids+1))); } foreach(l, namelist) { - char *curname = (char *) lfirst(l); - Oid relOid = RangeVarGetRelid(makeRangeVarFromNameList(stringToQualifiedNameList(curname)), -#if PG_VERSION_NUM >= 90200 - NoLock, + char *curname = (char *) lfirst(l); +#if PG_VERSION_NUM >= 160000 + Oid relOid = RangeVarGetRelid(makeRangeVarFromNameList( + stringToQualifiedNameList(curname, NULL)), NoLock, true); +#elif PG_VERSION_NUM >= 90200 + Oid relOid = RangeVarGetRelid(makeRangeVarFromNameList( + stringToQualifiedNameList(curname)), NoLock, true); +#else + Oid relOid = RangeVarGetRelid(makeRangeVarFromNameList( + stringToQualifiedNameList(curname)), true); #endif - true); if (relOid == InvalidOid) { @@ -211,10 +294,10 @@ includeTablesCheck(char **newval, void **extra, GucSource source) static void includeTablesAssign(const char *newval, void *extra) { - tableListAssign(newval, true, &excludeTables); + tableListAssign(newval, true, &includeTables); } -#else /* PG_VERSION_NUM < 90100 */ +#else /* PG_VERSION_NUM < 90100 */ static const char * excludeTablesAssign(const char * newval, bool doit, GucSource source) @@ -230,21 +313,48 @@ includeTablesAssign(const char * newval, bool doit, GucSource source) #endif +static void +lateInit() +{ + TableList *tl[] = {&includeTables, &excludeTables}; + int i; + +#if PG_VERSION_NUM >= 170000 + if (MyProcNumber == INVALID_PROC_NUMBER || +#else + if (MyBackendId == InvalidBackendId || +#endif + !IsUnderPostmaster || + !IsTransactionState()) + return; /* we aren't in connected state */ + + for(i=0; iinited == false) + tableListAssign(tbl->tableStr, true, tbl); + tbl->inited = true; + } +} + static const char* tableListShow(TableList *tbl) { - char *val, *ptr; - int i, + char *val, *ptr; + int i, len; + lateInit(); + len = 1 /* \0 */ + tbl->nTables * (2 * NAMEDATALEN + 2 /* ', ' */ + 1 /* . */); ptr = val = palloc(len); *ptr ='\0'; for(i=0; inTables; i++) { - char *relname = get_rel_name(tbl->tables[i]); - Oid nspOid = get_rel_namespace(tbl->tables[i]); - char *nspname = get_namespace_name(nspOid); + char *relname = get_rel_name(tbl->tables[i]); + Oid nspOid = get_rel_namespace(tbl->tables[i]); + char *nspname = get_namespace_name(nspOid); if ( relname == NULL || nspOid == InvalidOid || nspname == NULL ) continue; @@ -292,89 +402,211 @@ matchOid(TableList *tbl, Oid oid) return false; } -static ExecutorEnd_hook_type oldhook = NULL; +#if PG_VERSION_NUM >= 90500 +static RangeVar* +makeRangeVarFromOid(Oid relOid) +{ + return makeRangeVar( + get_namespace_name(get_rel_namespace(relOid)), + get_rel_name(relOid), + -1 + ); + +} +#endif static void -makeAnalyze(Oid relOid, CmdType operation, uint32 naffected) +makeAnalyze(Oid relOid, CmdKind operation, int64 naffected) { - PgStat_StatTabEntry *tabentry; - TimestampTz now = GetCurrentTimestamp(); + TimestampTz now = GetCurrentTimestamp(); + Relation rel; + OnlineAnalyzeTableType reltype; + bool found = false, + newTable = false; + OnlineAnalyzeTableStat *rstat, + dummyrstat; + PgStat_StatTabEntry *tabentry = NULL; if (relOid == InvalidOid) return; - tabentry = pgstat_fetch_stat_tabentry(relOid); + if (naffected == 0) + /* return if there is no changes */ + return; + else if (naffected < 0) + /* number if affected rows is unknown */ + naffected = 0; -#if PG_VERSION_NUM >= 90000 -#define changes_since_analyze(t) ((t)->changes_since_analyze) -#else -#define changes_since_analyze(t) ((t)->n_live_tuples + (t)->n_dead_tuples - (t)->last_anl_tuples) -#endif - - if ( - tabentry == NULL /* a new table */ || - ( - /* do not analyze too often, if both stamps are exceeded the go */ - TimestampDifferenceExceeds(tabentry->analyze_timestamp, now, online_analyze_min_interval) && - TimestampDifferenceExceeds(tabentry->autovac_analyze_timestamp, now, online_analyze_min_interval) && - /* be in sync with relation_needs_vacanalyze */ - ((double)(changes_since_analyze(tabentry) + naffected)) >= - online_analyze_scale_factor * ((double)(tabentry->n_dead_tuples + tabentry->n_live_tuples)) + - (double)online_analyze_threshold - ) - ) + rel = RelationIdGetRelation(relOid); + if (rel->rd_rel->relkind != RELKIND_RELATION) { - VacuumStmt vacstmt; - TimestampTz startStamp, endStamp; + RelationClose(rel); + return; + } - /* - * includeTables overwrites excludeTables - */ - switch(online_analyze_table_type) - { - case OATT_ALL: - if (matchOid(&excludeTables, relOid) == true && matchOid(&includeTables, relOid) == false) - return; - break; - case OATT_NONE: + reltype = +#if PG_VERSION_NUM >= 90100 + (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) +#else + (rel->rd_istemp || rel->rd_islocaltemp) +#endif + ? OATT_TEMPORARY : OATT_PERSISTENT; + + RelationClose(rel); + + /* + * includeTables overwrites excludeTables + */ + switch(online_analyze_table_type) + { + case OATT_ALL: + if (get_rel_relkind(relOid) != RELKIND_RELATION || + (matchOid(&excludeTables, relOid) == true && + matchOid(&includeTables, relOid) == false)) + return; + break; + case OATT_NONE: + if (get_rel_relkind(relOid) != RELKIND_RELATION || + matchOid(&includeTables, relOid) == false) + return; + break; + case OATT_TEMPORARY: + case OATT_PERSISTENT: + default: + /* + * skip analyze if relation's type doesn't not match + * online_analyze_table_type + */ + if ((online_analyze_table_type & reltype) == 0 || + matchOid(&excludeTables, relOid) == true) + { if (matchOid(&includeTables, relOid) == false) return; - break; - case OATT_TEMPORARY: - case OATT_PERSISTENT: - default: - { - Relation rel; - OnlineAnalyzeTableType reltype; + } + break; + } - rel = RelationIdGetRelation(relOid); - reltype = -#if PG_VERSION_NUM >= 90100 - (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + /* + * Do not store data about persistent table in local memory because we + * could not track changes of them: they could be changed by another + * backends. So always get a pgstat table entry. + */ + if (reltype == OATT_TEMPORARY) + rstat = hash_search(relstats, &relOid, HASH_ENTER, &found); + else + rstat = &dummyrstat; /* found == false for following if */ + + if (!found) + { + MemSet(rstat, 0, sizeof(*rstat)); + rstat->tableid = relOid; + newTable = true; + } + + if (operation == CK_VACUUM) + { + /* force reread because vacuum could change n_tuples */ + rstat->rereadStat = true; + return; + } + else if (operation == CK_ANALYZE) + { + /* only analyze */ + rstat->mod_since_analyze = 0; + rstat->last_analyze_time = now; + if (newTable) + rstat->rereadStat = true; + return; + } + + Assert(rstat->tableid == relOid); + + 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 (tabentry) + { + rstat->n_tuples = +#if PG_VERSION_NUM >= 160000 + tabentry->dead_tuples + tabentry->live_tuples; #else - (rel->rd_istemp || rel->rd_islocaltemp) + tabentry->n_dead_tuples + tabentry->n_live_tuples; #endif - ? OATT_TEMPORARY : OATT_PERSISTENT; - RelationClose(rel); - /* - * skip analyze if relation's type doesn't not match online_analyze_table_type - */ - if ((online_analyze_table_type & reltype) == 0 || matchOid(&excludeTables, relOid) == true) - { - if (matchOid(&includeTables, relOid) == false) - return; - } - } - break; + rstat->mod_since_analyze = +#if PG_VERSION_NUM >= 160000 + tabentry->mod_since_analyze; +#elif PG_VERSION_NUM >= 90000 + tabentry->changes_since_analyze; +#else + tabentry->n_live_tuples + tabentry->n_dead_tuples - + tabentry->last_anl_tuples; +#endif + + rstat->last_autoanalyze_time = +#if PG_VERSION_NUM >= 160000 + tabentry->last_autoanalyze_time; +#else + tabentry->autovac_analyze_timestamp; +#endif + + rstat->last_analyze_time = +#if PG_VERSION_NUM >= 160000 + tabentry->last_analyze_time; +#else + tabentry->analyze_timestamp; +#endif } + } + + if (newTable || + /* 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->last_analyze_time, now, online_analyze_min_interval) && + TimestampDifferenceExceeds(rstat->last_autoanalyze_time, now, online_analyze_min_interval) && + /* do not analyze too small tables */ + rstat->n_tuples + rstat->mod_since_analyze + naffected > online_analyze_lower_limit && + /* be in sync with relation_needs_vacanalyze */ + ((double)(rstat->mod_since_analyze + naffected)) >= + online_analyze_scale_factor * ((double)rstat->n_tuples) + + (double)online_analyze_threshold)) + { +#if PG_VERSION_NUM < 90500 + VacuumStmt vacstmt; +#else + VacuumParams vacstmt; +#endif + TimestampTz startStamp, endStamp; + int flags; + +#ifdef PGPRO_EE + /* ATX is not compatible with online_analyze */ + if (getNestLevelATX() != 0) + return; +#endif + + memset(&startStamp, 0, sizeof(startStamp)); /* keep compiler quiet */ + + memset(&vacstmt, 0, sizeof(vacstmt)); - vacstmt.type = T_VacuumStmt; vacstmt.freeze_min_age = -1; vacstmt.freeze_table_age = -1; /* ??? */ + +#if PG_VERSION_NUM < 90500 + vacstmt.type = T_VacuumStmt; vacstmt.relation = NULL; vacstmt.va_cols = NIL; - #if PG_VERSION_NUM >= 90000 vacstmt.options = VACOPT_ANALYZE; if (online_analyze_verbose) @@ -384,16 +616,44 @@ makeAnalyze(Oid relOid, CmdType operation, uint32 naffected) vacstmt.analyze = true; vacstmt.verbose = online_analyze_verbose; #endif +#else + vacstmt.multixact_freeze_min_age = -1; + vacstmt.multixact_freeze_table_age = -1; + vacstmt.log_min_duration = -1; +#endif + if (online_analyze_verbose) startStamp = GetCurrentTimestamp(); - analyze_rel(relOid, &vacstmt, GetAccessStrategy(BAS_VACUUM) -#if (PG_VERSION_NUM < 90004) && (PG_VERSION_NUM >= 90000) + flags = VACOPT_ANALYZE | VACOPT_NOWAIT | + ((online_analyze_verbose) ? VACOPT_VERBOSE : 0); + +#if PG_VERSION_NUM >= 120000 + vacstmt.options = flags; +#endif + analyze_rel(relOid, +#if PG_VERSION_NUM < 90500 + &vacstmt +#if PG_VERSION_NUM >= 90018 + , true +#endif + , GetAccessStrategy(BAS_VACUUM) +#if (PG_VERSION_NUM >= 90000) && (PG_VERSION_NUM < 90004) , true +#endif +#else + makeRangeVarFromOid(relOid), +#if PG_VERSION_NUM < 120000 + flags, +#endif + &vacstmt, NULL, true, GetAccessStrategy(BAS_VACUUM) #endif ); + /* Make changes visible to subsequent calls */ + CommandCounterIncrement(); + if (online_analyze_verbose) { long secs; @@ -401,47 +661,175 @@ makeAnalyze(Oid relOid, CmdType operation, uint32 naffected) endStamp = GetCurrentTimestamp(); TimestampDifference(startStamp, endStamp, &secs, µsecs); - elog(INFO, "analyze \"%s\" took %.02f seconds", - get_rel_name(relOid), ((double)secs) + ((double)microsecs)/1.0e6); + elog(INFO, "analyze \"%s\" took %.02f seconds", + get_rel_name(relOid), + ((double)secs) + ((double)microsecs)/1.0e6); } + rstat->last_autoanalyze_time = now; + rstat->mod_since_analyze = 0; - if (tabentry == NULL) + switch(operation) { - /* new table */ - pgstat_clear_snapshot(); + case CK_CREATE: + case CK_INSERT: + case CK_UPDATE: + rstat->n_tuples += naffected; + /* FALLTHROUGH */ + case CK_DELETE: + rstat->rereadStat = (reltype == OATT_PERSISTENT); + break; + case CK_TRUNCATE: + case CK_FASTTRUNCATE: + rstat->rereadStat = false; + rstat->n_tuples = 0; + break; + default: + break; } - else + + /* update last analyze timestamp in local memory of backend */ + if (tabentry) { - /* update last analyze timestamp in local memory of backend */ +#if PG_VERSION_NUM >= 160000 + tabentry->last_analyze_time = now; + tabentry->mod_since_analyze = 0; +#else tabentry->analyze_timestamp = now; + tabentry->changes_since_analyze = 0; +#endif } +#if 0 + /* force reload stat for new table */ + if (newTable) + pgstat_clear_snapshot(); +#endif } -#if PG_VERSION_NUM >= 90000 - else if (tabentry != NULL) + else { - tabentry->changes_since_analyze += naffected; - } +#if PG_VERSION_NUM >= 90000 + if (tabentry) +#if PG_VERSION_NUM >= 160000 + tabentry->mod_since_analyze += naffected; +#else + tabentry->changes_since_analyze += naffected; +#endif #endif + switch(operation) + { + case CK_CREATE: + case CK_INSERT: + rstat->mod_since_analyze += naffected; + rstat->n_tuples += naffected; + break; + case CK_UPDATE: + rstat->mod_since_analyze += 2 * naffected; + rstat->n_tuples += naffected; + break; + case CK_DELETE: + rstat->mod_since_analyze += naffected; + break; + case CK_TRUNCATE: + case CK_FASTTRUNCATE: + rstat->mod_since_analyze = 0; + rstat->n_tuples = 0; + break; + default: + break; + } + } + + /* Reset local cache if we are over limit */ + if (hash_get_num_entries(relstats) > online_analyze_capacity_threshold) + 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) +onlineAnalyzeHooker(QueryDesc *queryDesc) { - uint32 naffected = 0; + int64 naffected = -1; + Const *constval; if (queryDesc->estate) - naffected = queryDesc->estate->es_processed; + naffected = queryDesc->estate->es_processed; + + lateInit(); - if (online_analyze_enable && queryDesc->plannedstmt && - (queryDesc->operation == CMD_INSERT || - queryDesc->operation == CMD_UPDATE || - queryDesc->operation == CMD_DELETE || #if PG_VERSION_NUM >= 90200 - 0 /* (queryDesc->operation == CMD_SELECT && queryDesc->dest && queryDesc->dest == DestIntoRel) */ + if (online_analyze_enable && + (constval = isFastTruncateCall(queryDesc)) != NULL) + { + Datum tblnamed = constval->constvalue; + char *tblname = text_to_cstring(DatumGetTextP(tblnamed)); +#if PG_VERSION_NUM >= 160000 + RangeVar *tblvar = + makeRangeVarFromNameList(stringToQualifiedNameList(tblname, NULL)); #else - (queryDesc->operation == CMD_SELECT && queryDesc->plannedstmt->intoClause) + RangeVar *tblvar = + makeRangeVarFromNameList(stringToQualifiedNameList(tblname)); +#endif + + makeAnalyze(RangeVarGetRelid(tblvar, + NoLock, + false), + CK_FASTTRUNCATE, -1); + } +#endif + + if (online_analyze_enable && queryDesc->plannedstmt && + (queryDesc->operation == CMD_INSERT || + queryDesc->operation == CMD_UPDATE || + queryDesc->operation == CMD_DELETE +#if PG_VERSION_NUM < 90200 + || (queryDesc->operation == CMD_SELECT && + queryDesc->plannedstmt->intoClause) #endif )) { @@ -452,7 +840,7 @@ onlineAnalyzeHooker(QueryDesc *queryDesc) makeAnalyze(relOid, queryDesc->operation, naffected); } - else + else #endif if (queryDesc->plannedstmt->resultRelations && queryDesc->plannedstmt->rtable) @@ -461,29 +849,331 @@ onlineAnalyzeHooker(QueryDesc *queryDesc) foreach(l, queryDesc->plannedstmt->resultRelations) { - int n = lfirst_int(l); + int n = lfirst_int(l); 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); } } } - if (oldhook) - (*oldhook)(queryDesc); + if (oldExecutorEndHook) + oldExecutorEndHook(queryDesc); else 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 >= 120000 +static int +parse_vacuum_opt(VacuumStmt *vacstmt) +{ + int options = vacstmt->is_vacuumcmd ? VACOPT_VACUUM : VACOPT_ANALYZE; + ListCell *lc; + + foreach(lc, vacstmt->options) + { + DefElem *opt = (DefElem *) lfirst(lc); + + /* Parse common options for VACUUM and ANALYZE */ + if (strcmp(opt->defname, "verbose") == 0) + options |= VACOPT_VERBOSE; + else if (strcmp(opt->defname, "skip_locked") == 0) + options |= VACOPT_SKIP_LOCKED; + else if (strcmp(opt->defname, "analyze") == 0) + options |= VACOPT_ANALYZE; + else if (strcmp(opt->defname, "freeze") == 0) + options |= VACOPT_FREEZE; + else if (strcmp(opt->defname, "full") == 0) + options |= VACOPT_FULL; + else if (strcmp(opt->defname, "disable_page_skipping") == 0) + options |= VACOPT_DISABLE_PAGE_SKIPPING; + } + + return options; +} +#endif + + +#if PG_VERSION_NUM >= 90200 +static void +onlineAnalyzeHookerUtility( +#if PG_VERSION_NUM >= 100000 + PlannedStmt *pstmt, +#else + Node *parsetree, +#endif + const char *queryString, +#if PG_VERSION_NUM >= 140000 + bool readOnlyTree, +#endif +#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, +#if PG_VERSION_NUM >= 130000 + QueryCompletion *completionTag +#else + char *completionTag +#endif +) { + List *tblnames = NIL; + CmdKind op = CK_INSERT; +#if PG_VERSION_NUM >= 100000 + Node *parsetree = NULL; + + if (pstmt->commandType == CMD_UTILITY) + parsetree = pstmt->utilityStmt; +#endif + + lateInit(); + + 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; + } + 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; + int options = +#if PG_VERSION_NUM >= 120000 + parse_vacuum_opt(vac) +#else + vac->options +#endif + ; + + +#if PG_VERSION_NUM >= 110000 + tblnames = vac->rels; +#else + if (vac->relation) + tblnames = list_make1(vac->relation); +#endif + + if (options & (VACOPT_VACUUM | VACOPT_FULL | VACOPT_FREEZE)) + { + /* optionally with analyze */ + op = CK_VACUUM; + + /* drop all collected stat */ + if (tblnames == NIL) + relstatsInit(); + } + else if (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->mod_since_analyze = 0; + rstat->last_analyze_time = now; + } + } + } + else + tblnames = NIL; + } + } + +#if PG_VERSION_NUM >= 100000 +#define parsetree pstmt +#endif + + if (oldProcessUtilityHook) + oldProcessUtilityHook(parsetree, queryString, +#if PG_VERSION_NUM >= 140000 + readOnlyTree, +#endif +#if PG_VERSION_NUM >= 90300 + context, params, +#if PG_VERSION_NUM >= 100000 + queryEnv, +#endif +#else + params, isTopLevel, +#endif + dest, completionTag); + else + standard_ProcessUtility(parsetree, queryString, +#if PG_VERSION_NUM >= 140000 + readOnlyTree, +#endif +#if PG_VERSION_NUM >= 90300 + context, params, +#if PG_VERSION_NUM >= 100000 + queryEnv, +#endif +#else + params, isTopLevel, +#endif + dest, completionTag); + +#if PG_VERSION_NUM >= 100000 +#undef parsetree +#endif + + if (tblnames) { + ListCell *l; + + foreach(l, tblnames) + { + 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); + } + } +} +#endif + + +static void +relstatsInit(void) +{ + HASHCTL hash_ctl; + int flags = 0; + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + + hash_ctl.hash = oid_hash; + flags |= HASH_FUNCTION; + + if (onlineAnalyzeMemoryContext) + { + Assert(relstats != NULL); + MemoryContextReset(onlineAnalyzeMemoryContext); + } + else + { + Assert(relstats == NULL); + +#if PG_VERSION_NUM < 90600 + onlineAnalyzeMemoryContext = + AllocSetContextCreate(CacheMemoryContext, + "online_analyze storage context", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE + ); +#else + onlineAnalyzeMemoryContext = + AllocSetContextCreate(CacheMemoryContext, + "online_analyze storage context", ALLOCSET_DEFAULT_SIZES); +#endif + } + + hash_ctl.hcxt = onlineAnalyzeMemoryContext; + flags |= HASH_CONTEXT; + + hash_ctl.keysize = sizeof(Oid); + + hash_ctl.entrysize = sizeof(OnlineAnalyzeTableStat); + flags |= HASH_ELEM; + + relstats = hash_create("online_analyze storage", 1024, &hash_ctl, flags); +} + void _PG_init(void); void _PG_init(void) { - oldhook = ExecutorEnd_hook; + relstatsInit(); + + oldExecutorEndHook = ExecutorEnd_hook; ExecutorEnd_hook = onlineAnalyzeHooker; +#if PG_VERSION_NUM >= 90200 + oldProcessUtilityHook = ProcessUtility_hook; + + ProcessUtility_hook = onlineAnalyzeHookerUtility; +#endif + + DefineCustomBoolVariable( "online_analyze.enable", "Enable on-line analyze", @@ -503,6 +1193,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", @@ -522,7 +1231,7 @@ _PG_init(void) NULL ); - DefineCustomRealVariable( + DefineCustomRealVariable( "online_analyze.scale_factor", "fraction of table size to start on-line analyze", "fraction of table size to start on-line analyze", @@ -543,7 +1252,7 @@ _PG_init(void) NULL ); - DefineCustomIntVariable( + DefineCustomIntVariable( "online_analyze.threshold", "min number of row updates before on-line analyze", "min number of row updates before on-line analyze", @@ -564,11 +1273,32 @@ _PG_init(void) NULL ); - DefineCustomRealVariable( + DefineCustomIntVariable( + "online_analyze.capacity_threshold", + "Max local cache table capacity", + "Max local cache table capacity", + &online_analyze_capacity_threshold, +#if PG_VERSION_NUM >= 80400 + online_analyze_capacity_threshold, +#endif + 0, + 0x7fffffff, + PGC_USERSET, +#if PG_VERSION_NUM >= 80400 + GUC_NOT_IN_SAMPLE, +#if PG_VERSION_NUM >= 90100 + NULL, +#endif +#endif + NULL, + NULL + ); + + DefineCustomRealVariable( "online_analyze.min_interval", "minimum time interval between analyze call (in milliseconds)", "minimum time interval between analyze call (in milliseconds)", - &online_analyze_scale_factor, + &online_analyze_min_interval, #if PG_VERSION_NUM >= 80400 online_analyze_min_interval, #endif @@ -585,7 +1315,7 @@ _PG_init(void) NULL ); -DefineCustomEnumVariable( + DefineCustomEnumVariable( "online_analyze.table_type", "Type(s) of table for online analyze: all(default), persistent, temporary, none", NULL, @@ -596,7 +1326,7 @@ DefineCustomEnumVariable( online_analyze_table_type_options, PGC_USERSET, #if PG_VERSION_NUM >= 80400 - GUC_NOT_IN_SAMPLE, + GUC_NOT_IN_SAMPLE, #if PG_VERSION_NUM >= 90100 NULL, #endif @@ -605,7 +1335,7 @@ DefineCustomEnumVariable( NULL ); - DefineCustomStringVariable( + DefineCustomStringVariable( "online_analyze.exclude_tables", "List of tables which will not online analyze", NULL, @@ -624,7 +1354,7 @@ DefineCustomEnumVariable( excludeTablesShow ); - DefineCustomStringVariable( + DefineCustomStringVariable( "online_analyze.include_tables", "List of tables which will online analyze", NULL, @@ -642,19 +1372,27 @@ DefineCustomEnumVariable( #endif includeTablesShow ); -} - -void _PG_fini(void); -void -_PG_fini(void) -{ - ExecutorEnd_hook = oldhook; - if (excludeTables.tables) - free(excludeTables.tables); - if (includeTables.tables) - free(includeTables.tables); + 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 + ); - excludeTables.tables = includeTables.tables = NULL; - excludeTables.nTables = includeTables.nTables = 0; + RegisterXactCallback(removeTable, NULL); }