aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/cache
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2008-09-09 18:58:09 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2008-09-09 18:58:09 +0000
commitee33b95d9c2ecec170bc517783d7268a4bd0c793 (patch)
tree9012453a44799d20b15b2e4dcb1fb5e6784e2a7e /src/backend/utils/cache
parentc06629c72e7e3d435e207c2f80de3aa8a97c1d04 (diff)
downloadpostgresql-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.c84
-rw-r--r--src/backend/utils/cache/plancache.c260
-rw-r--r--src/backend/utils/cache/ts_cache.c15
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;