aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2013-01-04 17:42:19 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2013-01-04 17:42:19 -0500
commit94afbd5831fbc1926f1c367ac14a45ccc29d313d (patch)
tree51cea3e79490853693f795ac03c2438dd9b5522a /src/backend/utils
parent78a5e738e97b4dda89e1bfea60675bcf15f25994 (diff)
downloadpostgresql-94afbd5831fbc1926f1c367ac14a45ccc29d313d.tar.gz
postgresql-94afbd5831fbc1926f1c367ac14a45ccc29d313d.zip
Invent a "one-shot" variant of CachedPlans for better performance.
SPI_execute() and related functions create a CachedPlan, execute it once, and immediately discard it, so that the functionality offered by plancache.c is of no value in this code path. And performance measurements show that the extra data copying and invalidation checking done by plancache.c slows down simple queries by 10% or more compared to 9.1. However, enough of the SPI code is shared with functions that do need plan caching that it seems impractical to bypass plancache.c altogether. Instead, let's invent a variant version of cached plans that preserves 99% of the API but doesn't offer any of the actual functionality, nor the overhead. This puts SPI_execute() performance back on par, or maybe even slightly better, than it was before. This change should resolve recent complaints of performance degradation from Dong Ye, Pavel Stehule, and others. By avoiding data copying, this change also reduces the amount of memory needed to execute many-statement SPI_execute() strings, as for instance in a recent complaint from Tomas Vondra. An additional benefit of this change is that multi-statement SPI_execute() query strings are now processed fully serially, that is we complete execution of earlier statements before running parse analysis and planning on following ones. This eliminates a long-standing POLA violation, in that DDL that affects the behavior of a later statement will now behave as expected. Back-patch to 9.2, since this was a performance regression compared to 9.1. (In 9.2, place the added struct fields so as to avoid changing the offsets of existing fields.) Heikki Linnakangas and Tom Lane
Diffstat (limited to 'src/backend/utils')
-rw-r--r--src/backend/utils/cache/plancache.c191
1 files changed, 162 insertions, 29 deletions
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index f509f96ca7f..cbc7c498d0d 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -181,6 +181,7 @@ CreateCachedPlan(Node *raw_parse_tree,
plansource->invalItems = NIL;
plansource->query_context = NULL;
plansource->gplan = NULL;
+ plansource->is_oneshot = false;
plansource->is_complete = false;
plansource->is_saved = false;
plansource->is_valid = false;
@@ -196,6 +197,69 @@ CreateCachedPlan(Node *raw_parse_tree,
}
/*
+ * CreateOneShotCachedPlan: initially create a one-shot plan cache entry.
+ *
+ * This variant of CreateCachedPlan creates a plan cache entry that is meant
+ * to be used only once. No data copying occurs: all data structures remain
+ * in the caller's memory context (which typically should get cleared after
+ * completing execution). The CachedPlanSource struct itself is also created
+ * in that context.
+ *
+ * A one-shot plan cannot be saved or copied, since we make no effort to
+ * preserve the raw parse tree unmodified. There is also no support for
+ * invalidation, so plan use must be completed in the current transaction,
+ * and DDL that might invalidate the querytree_list must be avoided as well.
+ *
+ * raw_parse_tree: output of raw_parser()
+ * query_string: original query text
+ * commandTag: compile-time-constant tag for query, or NULL if empty query
+ */
+CachedPlanSource *
+CreateOneShotCachedPlan(Node *raw_parse_tree,
+ const char *query_string,
+ const char *commandTag)
+{
+ CachedPlanSource *plansource;
+
+ Assert(query_string != NULL); /* required as of 8.4 */
+
+ /*
+ * Create and fill the CachedPlanSource struct within the caller's memory
+ * context. Most fields are just left empty for the moment.
+ */
+ plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
+ plansource->magic = CACHEDPLANSOURCE_MAGIC;
+ plansource->raw_parse_tree = raw_parse_tree;
+ plansource->query_string = query_string;
+ plansource->commandTag = commandTag;
+ plansource->param_types = NULL;
+ plansource->num_params = 0;
+ plansource->parserSetup = NULL;
+ plansource->parserSetupArg = NULL;
+ plansource->cursor_options = 0;
+ plansource->fixed_result = false;
+ plansource->resultDesc = NULL;
+ plansource->search_path = NULL;
+ plansource->context = CurrentMemoryContext;
+ plansource->query_list = NIL;
+ plansource->relationOids = NIL;
+ plansource->invalItems = NIL;
+ plansource->query_context = NULL;
+ plansource->gplan = NULL;
+ plansource->is_oneshot = true;
+ plansource->is_complete = false;
+ plansource->is_saved = false;
+ plansource->is_valid = false;
+ plansource->generation = 0;
+ plansource->next_saved = NULL;
+ plansource->generic_cost = -1;
+ plansource->total_custom_cost = 0;
+ plansource->num_custom_plans = 0;
+
+ return plansource;
+}
+
+/*
* CompleteCachedPlan: second step of creating a plan cache entry.
*
* Pass in the analyzed-and-rewritten form of the query, as well as the
@@ -222,6 +286,10 @@ CreateCachedPlan(Node *raw_parse_tree,
* option, it is caller's responsibility that the referenced data remains
* valid for as long as the CachedPlanSource exists.
*
+ * If the CachedPlanSource is a "oneshot" plan, then no querytree copying
+ * occurs at all, and querytree_context is ignored; it is caller's
+ * responsibility that the passed querytree_list is sufficiently long-lived.
+ *
* plansource: structure returned by CreateCachedPlan
* querytree_list: analyzed-and-rewritten form of query (list of Query nodes)
* querytree_context: memory context containing querytree_list,
@@ -254,9 +322,15 @@ CompleteCachedPlan(CachedPlanSource *plansource,
/*
* If caller supplied a querytree_context, reparent it underneath the
* CachedPlanSource's context; otherwise, create a suitable context and
- * copy the querytree_list into it.
+ * copy the querytree_list into it. But no data copying should be done
+ * for one-shot plans; for those, assume the passed querytree_list is
+ * sufficiently long-lived.
*/
- if (querytree_context != NULL)
+ if (plansource->is_oneshot)
+ {
+ querytree_context = CurrentMemoryContext;
+ }
+ else if (querytree_context != NULL)
{
MemoryContextSetParent(querytree_context, source_context);
MemoryContextSwitchTo(querytree_context);
@@ -279,11 +353,12 @@ CompleteCachedPlan(CachedPlanSource *plansource,
/*
* Use the planner machinery to extract dependencies. Data is saved in
* query_context. (We assume that not a lot of extra cruft is created by
- * this call.)
+ * this call.) We can skip this for one-shot plans.
*/
- extract_query_dependencies((Node *) querytree_list,
- &plansource->relationOids,
- &plansource->invalItems);
+ if (!plansource->is_oneshot)
+ extract_query_dependencies((Node *) querytree_list,
+ &plansource->relationOids,
+ &plansource->invalItems);
/*
* Save the final parameter types (or other parameter specification data)
@@ -326,7 +401,8 @@ CompleteCachedPlan(CachedPlanSource *plansource,
* it to the list of cached plans that are checked for invalidation when an
* sinval event occurs.
*
- * This is guaranteed not to throw error; callers typically depend on that
+ * This is guaranteed not to throw error, except for the caller-error case
+ * of trying to save a one-shot plan. Callers typically depend on that
* since this is called just before or just after adding a pointer to the
* CachedPlanSource to some permanent data structure of their own. Up until
* this is done, a CachedPlanSource is just transient data that will go away
@@ -340,6 +416,10 @@ SaveCachedPlan(CachedPlanSource *plansource)
Assert(plansource->is_complete);
Assert(!plansource->is_saved);
+ /* This seems worth a real test, though */
+ if (plansource->is_oneshot)
+ elog(ERROR, "cannot save one-shot cached plan");
+
/*
* In typical use, this function would be called before generating any
* plans from the CachedPlanSource. If there is a generic plan, moving it
@@ -402,11 +482,15 @@ DropCachedPlan(CachedPlanSource *plansource)
/* Decrement generic CachePlan's refcount and drop if no longer needed */
ReleaseGenericPlan(plansource);
+ /* Mark it no longer valid */
+ plansource->magic = 0;
+
/*
* Remove the CachedPlanSource and all subsidiary data (including the
- * query_context if any).
+ * query_context if any). But if it's a one-shot we can't free anything.
*/
- MemoryContextDelete(plansource->context);
+ if (!plansource->is_oneshot)
+ MemoryContextDelete(plansource->context);
}
/*
@@ -452,6 +536,17 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
MemoryContext oldcxt;
/*
+ * For one-shot plans, we do not support revalidation checking; it's
+ * assumed the query is parsed, planned, and executed in one transaction,
+ * so that no lock re-acquisition is necessary.
+ */
+ if (plansource->is_oneshot)
+ {
+ Assert(plansource->is_valid);
+ return NIL;
+ }
+
+ /*
* If the query is currently valid, acquire locks on the referenced
* objects; then check again. We need to do it this way to cover the race
* condition that an invalidation message arrives before we get the locks.
@@ -649,6 +744,8 @@ CheckCachedPlan(CachedPlanSource *plansource)
return false;
Assert(plan->magic == CACHEDPLAN_MAGIC);
+ /* Generic plans are never one-shot */
+ Assert(!plan->is_oneshot);
/*
* If it appears valid, acquire locks and recheck; this is much the same
@@ -708,7 +805,8 @@ CheckCachedPlan(CachedPlanSource *plansource)
* hint rather than a hard constant.
*
* Planning work is done in the caller's memory context. The finished plan
- * is in a child memory context, which typically should get reparented.
+ * is in a child memory context, which typically should get reparented
+ * (unless this is a one-shot plan, in which case we don't copy the plan).
*/
static CachedPlan *
BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
@@ -719,7 +817,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
bool snapshot_set;
bool spi_pushed;
MemoryContext plan_context;
- MemoryContext oldcxt;
+ MemoryContext oldcxt = CurrentMemoryContext;
/*
* Normally the querytree should be valid already, but if it's not,
@@ -739,10 +837,16 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
/*
* If we don't already have a copy of the querytree list that can be
- * scribbled on by the planner, make one.
+ * scribbled on by the planner, make one. For a one-shot plan, we assume
+ * it's okay to scribble on the original query_list.
*/
if (qlist == NIL)
- qlist = (List *) copyObject(plansource->query_list);
+ {
+ if (!plansource->is_oneshot)
+ qlist = (List *) copyObject(plansource->query_list);
+ else
+ qlist = plansource->query_list;
+ }
/*
* Restore the search_path that was in use when the plan was made. See
@@ -794,22 +898,29 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
PopOverrideSearchPath();
/*
- * Make a dedicated memory context for the CachedPlan and its subsidiary
- * data. It's probably not going to be large, but just in case, use the
- * default maxsize parameter. It's transient for the moment.
+ * Normally we make a dedicated memory context for the CachedPlan and its
+ * subsidiary data. (It's probably not going to be large, but just in
+ * case, use the default maxsize parameter. It's transient for the
+ * moment.) But for a one-shot plan, we just leave it in the caller's
+ * memory context.
*/
- plan_context = AllocSetContextCreate(CurrentMemoryContext,
- "CachedPlan",
- ALLOCSET_SMALL_MINSIZE,
- ALLOCSET_SMALL_INITSIZE,
- ALLOCSET_DEFAULT_MAXSIZE);
+ if (!plansource->is_oneshot)
+ {
+ plan_context = AllocSetContextCreate(CurrentMemoryContext,
+ "CachedPlan",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
- /*
- * Copy plan into the new context.
- */
- oldcxt = MemoryContextSwitchTo(plan_context);
+ /*
+ * Copy plan into the new context.
+ */
+ MemoryContextSwitchTo(plan_context);
- plist = (List *) copyObject(plist);
+ plist = (List *) copyObject(plist);
+ }
+ else
+ plan_context = CurrentMemoryContext;
/*
* Create and fill the CachedPlan struct within the new context.
@@ -826,6 +937,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
plan->saved_xmin = InvalidTransactionId;
plan->refcount = 0;
plan->context = plan_context;
+ plan->is_oneshot = plansource->is_oneshot;
plan->is_saved = false;
plan->is_valid = true;
@@ -847,7 +959,11 @@ choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
{
double avg_custom_cost;
- /* Never any point in a custom plan if there's no parameters */
+ /* One-shot plans will always be considered custom */
+ if (plansource->is_oneshot)
+ return true;
+
+ /* Otherwise, never any point in a custom plan if there's no parameters */
if (boundParams == NULL)
return false;
@@ -1049,7 +1165,14 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
Assert(plan->refcount > 0);
plan->refcount--;
if (plan->refcount == 0)
- MemoryContextDelete(plan->context);
+ {
+ /* Mark it no longer valid */
+ plan->magic = 0;
+
+ /* One-shot plans do not own their context, so we can't free them */
+ if (!plan->is_oneshot)
+ MemoryContextDelete(plan->context);
+ }
}
/*
@@ -1066,9 +1189,11 @@ CachedPlanSetParentContext(CachedPlanSource *plansource,
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->is_complete);
- /* This seems worth a real test, though */
+ /* These seem worth real tests, though */
if (plansource->is_saved)
elog(ERROR, "cannot move a saved cached plan to another context");
+ if (plansource->is_oneshot)
+ elog(ERROR, "cannot move a one-shot cached plan to another context");
/* OK, let the caller keep the plan where he wishes */
MemoryContextSetParent(plansource->context, newcontext);
@@ -1105,6 +1230,13 @@ CopyCachedPlan(CachedPlanSource *plansource)
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->is_complete);
+ /*
+ * One-shot plans can't be copied, because we haven't taken care that
+ * parsing/planning didn't scribble on the raw parse tree or querytrees.
+ */
+ if (plansource->is_oneshot)
+ elog(ERROR, "cannot copy a one-shot cached plan");
+
source_context = AllocSetContextCreate(CurrentMemoryContext,
"CachedPlanSource",
ALLOCSET_SMALL_MINSIZE,
@@ -1152,6 +1284,7 @@ CopyCachedPlan(CachedPlanSource *plansource)
newsource->gplan = NULL;
+ newsource->is_oneshot = false;
newsource->is_complete = true;
newsource->is_saved = false;
newsource->is_valid = plansource->is_valid;