diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2008-09-09 18:58:09 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2008-09-09 18:58:09 +0000 |
commit | ee33b95d9c2ecec170bc517783d7268a4bd0c793 (patch) | |
tree | 9012453a44799d20b15b2e4dcb1fb5e6784e2a7e /src/backend/utils/cache | |
parent | c06629c72e7e3d435e207c2f80de3aa8a97c1d04 (diff) | |
download | postgresql-ee33b95d9c2ecec170bc517783d7268a4bd0c793.tar.gz postgresql-ee33b95d9c2ecec170bc517783d7268a4bd0c793.zip |
Improve the plan cache invalidation mechanism to make it invalidate plans
when user-defined functions used in a plan are modified. Also invalidate
plans when schemas, operators, or operator classes are modified; but for these
cases we just invalidate everything rather than tracking exact dependencies,
since these types of objects seldom change in a production database.
Tom Lane; loosely based on a patch by Martin Pihlak.
Diffstat (limited to 'src/backend/utils/cache')
-rw-r--r-- | src/backend/utils/cache/inval.c | 84 | ||||
-rw-r--r-- | src/backend/utils/cache/plancache.c | 260 | ||||
-rw-r--r-- | src/backend/utils/cache/ts_cache.c | 15 |
3 files changed, 211 insertions, 148 deletions
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c index 050d7cc88de..5c9451022d2 100644 --- a/src/backend/utils/cache/inval.c +++ b/src/backend/utils/cache/inval.c @@ -80,7 +80,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/cache/inval.c,v 1.86 2008/06/19 21:32:56 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/inval.c,v 1.87 2008/09/09 18:58:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -160,16 +160,25 @@ static TransInvalidationInfo *transInvalInfo = NULL; * assumes there won't be very many of these at once; could improve if needed. */ -#define MAX_CACHE_CALLBACKS 20 +#define MAX_SYSCACHE_CALLBACKS 20 +#define MAX_RELCACHE_CALLBACKS 5 -static struct CACHECALLBACK +static struct SYSCACHECALLBACK { - int16 id; /* cache number or message type id */ - CacheCallbackFunction function; + int16 id; /* cache number */ + SyscacheCallbackFunction function; Datum arg; -} cache_callback_list[MAX_CACHE_CALLBACKS]; +} syscache_callback_list[MAX_SYSCACHE_CALLBACKS]; -static int cache_callback_count = 0; +static int syscache_callback_count = 0; + +static struct RELCACHECALLBACK +{ + RelcacheCallbackFunction function; + Datum arg; +} relcache_callback_list[MAX_RELCACHE_CALLBACKS]; + +static int relcache_callback_count = 0; /* info values for 2PC callback */ #define TWOPHASE_INFO_MSG 0 /* SharedInvalidationMessage */ @@ -484,12 +493,13 @@ LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg) msg->cc.hashValue, &msg->cc.tuplePtr); - for (i = 0; i < cache_callback_count; i++) + for (i = 0; i < syscache_callback_count; i++) { - struct CACHECALLBACK *ccitem = cache_callback_list + i; + struct SYSCACHECALLBACK *ccitem = syscache_callback_list + i; if (ccitem->id == msg->cc.id) - (*ccitem->function) (ccitem->arg, InvalidOid); + (*ccitem->function) (ccitem->arg, + msg->cc.id, &msg->cc.tuplePtr); } } } @@ -499,12 +509,11 @@ LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg) { RelationCacheInvalidateEntry(msg->rc.relId); - for (i = 0; i < cache_callback_count; i++) + for (i = 0; i < relcache_callback_count; i++) { - struct CACHECALLBACK *ccitem = cache_callback_list + i; + struct RELCACHECALLBACK *ccitem = relcache_callback_list + i; - if (ccitem->id == SHAREDINVALRELCACHE_ID) - (*ccitem->function) (ccitem->arg, msg->rc.relId); + (*ccitem->function) (ccitem->arg, msg->rc.relId); } } } @@ -539,9 +548,16 @@ InvalidateSystemCaches(void) ResetCatalogCaches(); RelationCacheInvalidate(); /* gets smgr cache too */ - for (i = 0; i < cache_callback_count; i++) + for (i = 0; i < syscache_callback_count; i++) { - struct CACHECALLBACK *ccitem = cache_callback_list + i; + struct SYSCACHECALLBACK *ccitem = syscache_callback_list + i; + + (*ccitem->function) (ccitem->arg, ccitem->id, NULL); + } + + for (i = 0; i < relcache_callback_count; i++) + { + struct RELCACHECALLBACK *ccitem = relcache_callback_list + i; (*ccitem->function) (ccitem->arg, InvalidOid); } @@ -1177,26 +1193,25 @@ CacheInvalidateRelcacheByRelid(Oid relid) /* * CacheRegisterSyscacheCallback * Register the specified function to be called for all future - * invalidation events in the specified cache. + * invalidation events in the specified cache. The cache ID and the + * TID of the tuple being invalidated will be passed to the function. * - * NOTE: currently, the OID argument to the callback routine is not - * provided for syscache callbacks; the routine doesn't really get any - * useful info as to exactly what changed. It should treat every call - * as a "cache flush" request. + * NOTE: NULL will be passed for the TID if a cache reset request is received. + * In this case the called routines should flush all cached state. */ void CacheRegisterSyscacheCallback(int cacheid, - CacheCallbackFunction func, + SyscacheCallbackFunction func, Datum arg) { - if (cache_callback_count >= MAX_CACHE_CALLBACKS) - elog(FATAL, "out of cache_callback_list slots"); + if (syscache_callback_count >= MAX_SYSCACHE_CALLBACKS) + elog(FATAL, "out of syscache_callback_list slots"); - cache_callback_list[cache_callback_count].id = cacheid; - cache_callback_list[cache_callback_count].function = func; - cache_callback_list[cache_callback_count].arg = arg; + syscache_callback_list[syscache_callback_count].id = cacheid; + syscache_callback_list[syscache_callback_count].function = func; + syscache_callback_list[syscache_callback_count].arg = arg; - ++cache_callback_count; + ++syscache_callback_count; } /* @@ -1209,15 +1224,14 @@ CacheRegisterSyscacheCallback(int cacheid, * In this case the called routines should flush all cached state. */ void -CacheRegisterRelcacheCallback(CacheCallbackFunction func, +CacheRegisterRelcacheCallback(RelcacheCallbackFunction func, Datum arg) { - if (cache_callback_count >= MAX_CACHE_CALLBACKS) - elog(FATAL, "out of cache_callback_list slots"); + if (relcache_callback_count >= MAX_RELCACHE_CALLBACKS) + elog(FATAL, "out of relcache_callback_list slots"); - cache_callback_list[cache_callback_count].id = SHAREDINVALRELCACHE_ID; - cache_callback_list[cache_callback_count].function = func; - cache_callback_list[cache_callback_count].arg = arg; + relcache_callback_list[relcache_callback_count].function = func; + relcache_callback_list[relcache_callback_count].arg = arg; - ++cache_callback_count; + ++relcache_callback_count; } diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index c0dc3649ac0..bf1adf16ecc 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -12,7 +12,7 @@ * * The plan cache manager itself is principally responsible for tracking * whether cached plans should be invalidated because of schema changes in - * the tables they depend on. When (and if) the next demand for a cached + * the objects they depend on. When (and if) the next demand for a cached * plan occurs, the query will be replanned. Note that this could result * in an error, for example if a column referenced by the query is no * longer present. The creator of a cached plan can specify whether it @@ -20,20 +20,22 @@ * could happen with "SELECT *" for example) --- if so, it's up to the * caller to notice changes and cope with them. * - * Currently, we use only relcache invalidation events to invalidate plans. - * This means that changes such as modification of a function definition do - * not invalidate plans using the function. This is not 100% OK --- for - * example, changing a SQL function that's been inlined really ought to - * cause invalidation of the plan that it's been inlined into --- but the - * cost of tracking additional types of object seems much higher than the - * gain, so we're just ignoring them for now. + * Currently, we track exactly the dependencies of plans on relations and + * user-defined functions. On relcache invalidation events or pg_proc + * syscache invalidation events, we invalidate just those plans that depend + * on the particular object being modified. (Note: this scheme assumes + * that any table modification that requires replanning will generate a + * relcache inval event.) We also watch for inval events on certain other + * system catalogs, such as pg_namespace; but for them, our response is + * just to invalidate all plans. We expect updates on those catalogs to + * be infrequent enough that more-detailed tracking is not worth the effort. * * * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.20 2008/08/25 22:42:34 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.21 2008/09/09 18:58:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -44,6 +46,7 @@ #include "catalog/namespace.h" #include "executor/executor.h" #include "nodes/nodeFuncs.h" +#include "optimizer/planmain.h" #include "storage/lmgr.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" @@ -52,19 +55,7 @@ #include "utils/memutils.h" #include "utils/resowner.h" #include "utils/snapmgr.h" - - -typedef struct -{ - void (*callback) (); - void *arg; -} ScanQueryWalkerContext; - -typedef struct -{ - Oid inval_relid; - CachedPlan *plan; -} InvalRelidContext; +#include "utils/syscache.h" static List *cached_plans_list = NIL; @@ -73,28 +64,28 @@ static void StoreCachedPlan(CachedPlanSource *plansource, List *stmt_list, MemoryContext plan_context); static void AcquireExecutorLocks(List *stmt_list, bool acquire); static void AcquirePlannerLocks(List *stmt_list, bool acquire); -static void LockRelid(Oid relid, LOCKMODE lockmode, void *arg); -static void UnlockRelid(Oid relid, LOCKMODE lockmode, void *arg); -static void ScanQueryForRelids(Query *parsetree, - void (*callback) (), - void *arg); -static bool ScanQueryWalker(Node *node, ScanQueryWalkerContext *context); +static void ScanQueryForLocks(Query *parsetree, bool acquire); +static bool ScanQueryWalker(Node *node, bool *acquire); static bool rowmark_member(List *rowMarks, int rt_index); static bool plan_list_is_transient(List *stmt_list); -static void PlanCacheCallback(Datum arg, Oid relid); -static void InvalRelid(Oid relid, LOCKMODE lockmode, - InvalRelidContext *context); +static void PlanCacheRelCallback(Datum arg, Oid relid); +static void PlanCacheFuncCallback(Datum arg, int cacheid, ItemPointer tuplePtr); +static void PlanCacheSysCallback(Datum arg, int cacheid, ItemPointer tuplePtr); /* * InitPlanCache: initialize module during InitPostgres. * - * All we need to do is hook into inval.c's callback list. + * All we need to do is hook into inval.c's callback lists. */ void InitPlanCache(void) { - CacheRegisterRelcacheCallback(PlanCacheCallback, (Datum) 0); + CacheRegisterRelcacheCallback(PlanCacheRelCallback, (Datum) 0); + CacheRegisterSyscacheCallback(PROCOID, PlanCacheFuncCallback, (Datum) 0); + CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0); + CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0); + CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0); } /* @@ -337,6 +328,18 @@ StoreCachedPlan(CachedPlanSource *plansource, plan->refcount = 1; /* for the parent's link */ plan->generation = ++(plansource->generation); plan->context = plan_context; + if (plansource->fully_planned) + { + /* Planner already extracted dependencies, we don't have to */ + plan->relationOids = plan->invalItems = NIL; + } + else + { + /* Use the planner machinery to extract dependencies */ + extract_query_dependencies(stmt_list, + &plan->relationOids, + &plan->invalItems); + } Assert(plansource->plan == NULL); plansource->plan = plan; @@ -432,8 +435,8 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner) plan->dead = true; /* - * By now, if any invalidation has happened, PlanCacheCallback will - * have marked the plan dead. + * By now, if any invalidation has happened, the inval callback + * functions will have marked the plan dead. */ if (plan->dead) { @@ -637,37 +640,15 @@ AcquirePlannerLocks(List *stmt_list, bool acquire) Query *query = (Query *) lfirst(lc); Assert(IsA(query, Query)); - if (acquire) - ScanQueryForRelids(query, LockRelid, NULL); - else - ScanQueryForRelids(query, UnlockRelid, NULL); + ScanQueryForLocks(query, acquire); } } /* - * ScanQueryForRelids callback functions for AcquirePlannerLocks - */ -static void -LockRelid(Oid relid, LOCKMODE lockmode, void *arg) -{ - LockRelationOid(relid, lockmode); -} - -static void -UnlockRelid(Oid relid, LOCKMODE lockmode, void *arg) -{ - UnlockRelationOid(relid, lockmode); -} - -/* - * ScanQueryForRelids: recursively scan one Query and apply the callback - * function to each relation OID found therein. The callback function - * takes the arguments relation OID, lockmode, pointer arg. + * ScanQueryForLocks: recursively scan one Query for AcquirePlannerLocks. */ static void -ScanQueryForRelids(Query *parsetree, - void (*callback) (), - void *arg) +ScanQueryForLocks(Query *parsetree, bool acquire) { ListCell *lc; int rt_index; @@ -685,27 +666,22 @@ ScanQueryForRelids(Query *parsetree, switch (rte->rtekind) { case RTE_RELATION: - - /* - * Determine the lock type required for this RTE. - */ + /* Acquire or release the appropriate type of lock */ if (rt_index == parsetree->resultRelation) lockmode = RowExclusiveLock; else if (rowmark_member(parsetree->rowMarks, rt_index)) lockmode = RowShareLock; else lockmode = AccessShareLock; - - (*callback) (rte->relid, lockmode, arg); + if (acquire) + LockRelationOid(rte->relid, lockmode); + else + UnlockRelationOid(rte->relid, lockmode); break; case RTE_SUBQUERY: - - /* - * The subquery RTE itself is all right, but we have to - * recurse to process the represented subquery. - */ - ScanQueryForRelids(rte->subquery, callback, arg); + /* Recurse into subquery-in-FROM */ + ScanQueryForLocks(rte->subquery, acquire); break; default: @@ -720,21 +696,17 @@ ScanQueryForRelids(Query *parsetree, */ if (parsetree->hasSubLinks) { - ScanQueryWalkerContext context; - - context.callback = callback; - context.arg = arg; query_tree_walker(parsetree, ScanQueryWalker, - (void *) &context, + (void *) &acquire, QTW_IGNORE_RT_SUBQUERIES); } } /* - * Walker to find sublink subqueries for ScanQueryForRelids + * Walker to find sublink subqueries for ScanQueryForLocks */ static bool -ScanQueryWalker(Node *node, ScanQueryWalkerContext *context) +ScanQueryWalker(Node *node, bool *acquire) { if (node == NULL) return false; @@ -743,17 +715,16 @@ ScanQueryWalker(Node *node, ScanQueryWalkerContext *context) SubLink *sub = (SubLink *) node; /* Do what we came for */ - ScanQueryForRelids((Query *) sub->subselect, - context->callback, context->arg); + ScanQueryForLocks((Query *) sub->subselect, *acquire); /* Fall through to process lefthand args of SubLink */ } /* - * Do NOT recurse into Query nodes, because ScanQueryForRelids already + * Do NOT recurse into Query nodes, because ScanQueryForLocks already * processed subselects of subselects for us. */ return expression_tree_walker(node, ScanQueryWalker, - (void *) context); + (void *) acquire); } /* @@ -863,17 +834,16 @@ PlanCacheComputeResultDesc(List *stmt_list) } /* - * PlanCacheCallback + * PlanCacheRelCallback * Relcache inval callback function * * Invalidate all plans mentioning the given rel, or all plans mentioning * any rel at all if relid == InvalidOid. */ static void -PlanCacheCallback(Datum arg, Oid relid) +PlanCacheRelCallback(Datum arg, Oid relid) { ListCell *lc1; - ListCell *lc2; foreach(lc1, cached_plans_list) { @@ -885,6 +855,9 @@ PlanCacheCallback(Datum arg, Oid relid) continue; if (plan->fully_planned) { + /* Have to check the per-PlannedStmt relid lists */ + ListCell *lc2; + foreach(lc2, plan->stmt_list) { PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2); @@ -903,42 +876,117 @@ PlanCacheCallback(Datum arg, Oid relid) } else { - /* - * For not-fully-planned entries we use ScanQueryForRelids, since - * a recursive traversal is needed. The callback API is a bit - * tedious but avoids duplication of coding. - */ - InvalRelidContext context; + /* Otherwise check the single list we built ourselves */ + if ((relid == InvalidOid) ? plan->relationOids != NIL : + list_member_oid(plan->relationOids, relid)) + plan->dead = true; + } + } +} - context.inval_relid = relid; - context.plan = plan; +/* + * PlanCacheFuncCallback + * Syscache inval callback function for PROCOID cache + * + * Invalidate all plans mentioning the given catalog entry, or all plans + * mentioning any member of this cache if tuplePtr == NULL. + * + * Note that the coding would support use for multiple caches, but right + * now only user-defined functions are tracked this way. + */ +static void +PlanCacheFuncCallback(Datum arg, int cacheid, ItemPointer tuplePtr) +{ + ListCell *lc1; + + foreach(lc1, cached_plans_list) + { + CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1); + CachedPlan *plan = plansource->plan; + + /* No work if it's already invalidated */ + if (!plan || plan->dead) + continue; + if (plan->fully_planned) + { + /* Have to check the per-PlannedStmt inval-item lists */ + ListCell *lc2; foreach(lc2, plan->stmt_list) { - Query *query = (Query *) lfirst(lc2); + PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2); + ListCell *lc3; + + Assert(!IsA(plannedstmt, Query)); + if (!IsA(plannedstmt, PlannedStmt)) + continue; /* Ignore utility statements */ + foreach(lc3, plannedstmt->invalItems) + { + PlanInvalItem *item = (PlanInvalItem *) lfirst(lc3); + + if (item->cacheId != cacheid) + continue; + if (tuplePtr == NULL || + ItemPointerEquals(tuplePtr, &item->tupleId)) + { + /* Invalidate the plan! */ + plan->dead = true; + break; /* out of invalItems scan */ + } + } + if (plan->dead) + break; /* out of stmt_list scan */ + } + } + else + { + /* Otherwise check the single list we built ourselves */ + ListCell *lc2; + + foreach(lc2, plan->invalItems) + { + PlanInvalItem *item = (PlanInvalItem *) lfirst(lc2); - Assert(IsA(query, Query)); - ScanQueryForRelids(query, InvalRelid, (void *) &context); + if (item->cacheId != cacheid) + continue; + if (tuplePtr == NULL || + ItemPointerEquals(tuplePtr, &item->tupleId)) + { + /* Invalidate the plan! */ + plan->dead = true; + break; + } } } } } /* - * ResetPlanCache: drop all cached plans. + * PlanCacheSysCallback + * Syscache inval callback function for other caches + * + * Just invalidate everything... */ -void -ResetPlanCache(void) +static void +PlanCacheSysCallback(Datum arg, int cacheid, ItemPointer tuplePtr) { - PlanCacheCallback((Datum) 0, InvalidOid); + ResetPlanCache(); } /* - * ScanQueryForRelids callback function for PlanCacheCallback + * ResetPlanCache: drop all cached plans. */ -static void -InvalRelid(Oid relid, LOCKMODE lockmode, InvalRelidContext *context) +void +ResetPlanCache(void) { - if (relid == context->inval_relid || context->inval_relid == InvalidOid) - context->plan->dead = true; + ListCell *lc; + + foreach(lc, cached_plans_list) + { + CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); + CachedPlan *plan = plansource->plan; + + if (plan) + plan->dead = true; + } } diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c index 81ff577125e..33b415ac6f0 100644 --- a/src/backend/utils/cache/ts_cache.c +++ b/src/backend/utils/cache/ts_cache.c @@ -20,7 +20,7 @@ * Copyright (c) 2006-2008, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/cache/ts_cache.c,v 1.7 2008/04/12 23:14:21 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/ts_cache.c,v 1.8 2008/09/09 18:58:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -79,18 +79,19 @@ static Oid TSCurrentConfigCache = InvalidOid; /* - * We use this catcache callback to detect when a visible change to a TS + * We use this syscache callback to detect when a visible change to a TS * catalog entry has been made, by either our own backend or another one. - * We don't get enough information to know *which* specific catalog row - * changed, so we have to invalidate all related cache entries. Fortunately, - * it seems unlikely that TS configuration changes will occur often enough - * for this to be a performance problem. + * + * In principle we could just flush the specific cache entry that changed, + * but given that TS configuration changes are probably infrequent, it + * doesn't seem worth the trouble to determine that; we just flush all the + * entries of the related hash table. * * We can use the same function for all TS caches by passing the hash * table address as the "arg". */ static void -InvalidateTSCacheCallBack(Datum arg, Oid relid) +InvalidateTSCacheCallBack(Datum arg, int cacheid, ItemPointer tuplePtr) { HTAB *hash = (HTAB *) DatumGetPointer(arg); HASH_SEQ_STATUS status; |