One-Shot Plans
Currently, the planner and executor are mostly independent of each
other: the planner doesn't really know when the plan will be executed,
and the executor doesn't know how recently the plan was made.
We can work out the various paths through the traffic cop to see when
a plan will be a "one-shot" - planned and then executed immediately,
then discarded.
In those cases we can take advantage of better optimisations. Most
interestingly, we can evaluate stable functions at plan time, to allow
us to handle partitioning and partial indexes better.
Patch attached. Works...
SET constraint_exclusion = on;
ALTER TABLE <table> ADD CHECK (dt < current_date - 5);
SELECT * FROM <table> WHERE datecolumn >= current_date - 1;
QUERY PLAN
------------------------------------------
Result (cost=0.00..0.01 rows=1 width=0)
One-Time Filter: false
(2 rows)
WIP in the sense that we might want to change the special case
parameter handling as well.
Comments?
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
oneshot_plans.v2.patchapplication/octet-stream; name=oneshot_plans.v2.patchDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index a770daf..b77d023 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -282,6 +282,8 @@ ExplainOneQuery(Query *query, ExplainState *es,
return;
}
+ query->oneshot = true;
+
/* if an advisor plugin is present, let it manage things */
if (ExplainOneQuery_hook)
(*ExplainOneQuery_hook) (query, es, queryString, params);
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index d848926..7119ccd 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -701,6 +701,7 @@ execute_sql_string(const char *sql, const char *filename)
* Do parse analysis, rule rewrite, planning, and execution for each raw
* parsetree. We must fully execute each query before beginning parse
* analysis on the next one, since there may be interdependencies.
+ * Use one time plans so that we don't cache anything from this execution.
*/
foreach(lc1, raw_parsetree_list)
{
@@ -712,7 +713,7 @@ execute_sql_string(const char *sql, const char *filename)
sql,
NULL,
0);
- stmt_list = pg_plan_queries(stmt_list, 0, NULL);
+ stmt_list = pg_plan_queries(stmt_list, 0, NULL, true);
foreach(lc2, stmt_list)
{
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index dfa2ab0..6d8fd8c 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -145,7 +145,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
query_list = QueryRewrite(query);
/* Generate plans for queries. */
- plan_list = pg_plan_queries(query_list, 0, NULL);
+ plan_list = pg_plan_queries(query_list, 0, NULL, false);
/*
* Save the results.
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 6e723ca..cf87c6e 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1714,7 +1714,7 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
plan->argtypes,
plan->nargs);
}
- stmt_list = pg_plan_queries(stmt_list, cursor_options, boundParams);
+ stmt_list = pg_plan_queries(stmt_list, cursor_options, boundParams, false);
plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
cplan = (CachedPlan *) palloc0(sizeof(CachedPlan));
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 9aafc8a..2e322e6 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -170,6 +170,14 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
glob->lastRowMarkId = 0;
glob->transientPlan = false;
+ /*
+ * Record whether the plan will be used once and immediately by executor.
+ * If this is a oneshot plan we can evaluate pseudoconstants during
+ * planning, so we can take advantage of parameter values as constants,
+ * and new in 9.2, evaluate stable functions into constants.
+ */
+ glob->oneshot = parse->oneshot;
+
/* Determine what fraction of the plan is likely to be scanned */
if (cursorOptions & CURSOR_OPT_FAST_PLAN)
{
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 2914c39..a743920 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -61,6 +61,7 @@ typedef struct
List *active_fns;
Node *case_val;
bool estimate;
+ bool oneshot;
} eval_const_expressions_context;
typedef struct
@@ -2097,11 +2098,13 @@ eval_const_expressions(PlannerInfo *root, Node *node)
{
context.boundParams = root->glob->boundParams; /* bound Params */
context.glob = root->glob; /* for inlined-function dependencies */
+ context.oneshot = root->glob->oneshot; /* can we optimise for oneshot plans? */
}
else
{
context.boundParams = NULL;
context.glob = NULL;
+ context.oneshot = false;
}
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
@@ -2137,6 +2140,7 @@ estimate_expression_value(PlannerInfo *root, Node *node)
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = true; /* unsafe transformations OK */
+ context.oneshot = root->glob->oneshot; /* can we optimise for oneshot plans? */
return eval_const_expressions_mutator(node, &context);
}
@@ -3786,7 +3790,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
*/
if (funcform->provolatile == PROVOLATILE_IMMUTABLE)
/* okay */ ;
- else if (context->estimate && funcform->provolatile == PROVOLATILE_STABLE)
+ else if (funcform->provolatile == PROVOLATILE_STABLE && (context->estimate || context->oneshot))
/* okay */ ;
else
return NULL;
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index a07661f..ff9f3cd 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -762,7 +762,7 @@ pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams)
* list. Utility statements are simply represented by their statement nodes.
*/
List *
-pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams)
+pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams, bool oneshotplan)
{
List *stmt_list = NIL;
ListCell *query_list;
@@ -779,6 +779,7 @@ pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams)
}
else
{
+ query->oneshot = oneshotplan;
stmt = (Node *) pg_plan_query(query, cursorOptions, boundParams);
}
@@ -944,7 +945,7 @@ exec_simple_query(const char *query_string)
querytree_list = pg_analyze_and_rewrite(parsetree, query_string,
NULL, 0);
- plantree_list = pg_plan_queries(querytree_list, 0, NULL);
+ plantree_list = pg_plan_queries(querytree_list, 0, NULL, true);
/* Done with the snapshot used for parsing/planning */
if (snapshot_set)
@@ -1284,7 +1285,7 @@ exec_parse_message(const char *query_string, /* string to execute */
}
else
{
- stmt_list = pg_plan_queries(querytree_list, 0, NULL);
+ stmt_list = pg_plan_queries(querytree_list, 0, NULL, false);
fully_planned = true;
}
@@ -1739,7 +1740,7 @@ exec_bind_message(StringInfo input_message)
*/
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
query_list = copyObject(cplan->stmt_list);
- plan_list = pg_plan_queries(query_list, 0, params);
+ plan_list = pg_plan_queries(query_list, 0, params, true);
MemoryContextSwitchTo(oldContext);
/* We no longer need the cached plan refcount ... */
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 08ddfa9..e65ba82 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -563,7 +563,7 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
pushed = SPI_push_conditional();
- slist = pg_plan_queries(slist, plansource->cursor_options, NULL);
+ slist = pg_plan_queries(slist, plansource->cursor_options, NULL, false);
SPI_pop_conditional(pushed);
}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 14937d4..568262e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -103,6 +103,7 @@ typedef struct Query
QuerySource querySource; /* where did I come from? */
+ bool oneshot; /* can plan potentially be used more than once? */
bool canSetTag; /* do I set the command result tag? */
Node *utilityStmt; /* non-null if this is DECLARE CURSOR or a
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index f659269..e655542 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -76,6 +76,8 @@ typedef struct PlannerGlobal
ParamListInfo boundParams; /* Param values provided to planner() */
+ bool oneshot; /* Are we planning a non-reusable plan? */
+
List *paramlist; /* to keep track of cross-level Params */
List *subplans; /* Plans for SubPlan nodes */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index d5192d9..3be6467 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -55,7 +55,7 @@ extern List *pg_analyze_and_rewrite_params(Node *parsetree,
extern PlannedStmt *pg_plan_query(Query *querytree, int cursorOptions,
ParamListInfo boundParams);
extern List *pg_plan_queries(List *querytrees, int cursorOptions,
- ParamListInfo boundParams);
+ ParamListInfo boundParams, bool oneshotplan);
extern bool check_max_stack_depth(int *newval, void **extra, GucSource source);
extern void assign_max_stack_depth(int newval, void *extra);
Simon Riggs wrote:
Currently, the planner and executor are mostly independent of each
other: the planner doesn't really know when the plan will be executed,
and the executor doesn't know how recently the plan was made.We can work out the various paths through the traffic cop to see when
a plan will be a "one-shot" - planned and then executed immediately,
then discarded.
I was also hoping someday allow plans that are to be immediately
executed to probe the buffer cache to determine how expensive index
scans would be.
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ It's impossible for everything to be true. +
On Tue, Jun 14, 2011 at 7:28 PM, Bruce Momjian <bruce@momjian.us> wrote:
Simon Riggs wrote:
Currently, the planner and executor are mostly independent of each
other: the planner doesn't really know when the plan will be executed,
and the executor doesn't know how recently the plan was made.We can work out the various paths through the traffic cop to see when
a plan will be a "one-shot" - planned and then executed immediately,
then discarded.I was also hoping someday allow plans that are to be immediately
executed to probe the buffer cache to determine how expensive index
scans would be.
Yes, it opens up many optimizations, both for cache sensitivity and
dynamic data access.
But those are later ideas based on the existence of this first step.
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Simon Riggs wrote:
On Tue, Jun 14, 2011 at 7:28 PM, Bruce Momjian <bruce@momjian.us> wrote:
Simon Riggs wrote:
Currently, the planner and executor are mostly independent of each
other: the planner doesn't really know when the plan will be executed,
and the executor doesn't know how recently the plan was made.We can work out the various paths through the traffic cop to see when
a plan will be a "one-shot" - planned and then executed immediately,
then discarded.I was also hoping someday allow plans that are to be immediately
executed to probe the buffer cache to determine how expensive index
scans would be.Yes, it opens up many optimizations, both for cache sensitivity and
dynamic data access.But those are later ideas based on the existence of this first step.
Agreed.
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ It's impossible for everything to be true. +
Simon Riggs <simon@2ndQuadrant.com> writes:
Currently, the planner and executor are mostly independent of each
other: the planner doesn't really know when the plan will be executed,
and the executor doesn't know how recently the plan was made.
We can work out the various paths through the traffic cop to see when
a plan will be a "one-shot" - planned and then executed immediately,
then discarded.
I have already got plans for a significantly more sophisticated approach
to this.
In those cases we can take advantage of better optimisations. Most
interestingly, we can evaluate stable functions at plan time, to allow
us to handle partitioning and partial indexes better.
I don't believe that's correct in detail.
regards, tom lane
On Tue, Jun 14, 2011 at 1:25 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
We can work out the various paths through the traffic cop to see when
a plan will be a "one-shot" - planned and then executed immediately,
then discarded.In those cases we can take advantage of better optimisations. Most
interestingly, we can evaluate stable functions at plan time, to allow
us to handle partitioning and partial indexes better.Patch attached. Works...
this breaks test guc.c for me... specifically the test at
src/test/regress/sql/guc.sql circa line 226:
"""
set work_mem = '3MB';
-- but SET isn't
create or replace function myfunc(int) returns text as $$
begin
set work_mem = '2MB';
return current_setting('work_mem');
end $$
language plpgsql
set work_mem = '1MB';
select myfunc(0), current_setting('work_mem');
"""
regressions.diff
"""
*** 656,662 ****
select myfunc(0), current_setting('work_mem');
myfunc | current_setting
--------+-----------------
! 2MB | 2MB
(1 row)
set work_mem = '3MB';
--- 656,662 ----
select myfunc(0), current_setting('work_mem');
myfunc | current_setting
--------+-----------------
! 2MB | 3MB
(1 row)
set work_mem = '3MB';
"""
it seems that the effect of SET is being discarded
--
Jaime Casanova www.2ndQuadrant.com
Professional PostgreSQL: Soporte 24x7 y capacitación
On Tue, Jun 14, 2011 at 9:36 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Simon Riggs <simon@2ndQuadrant.com> writes:
Currently, the planner and executor are mostly independent of each
other: the planner doesn't really know when the plan will be executed,
and the executor doesn't know how recently the plan was made.We can work out the various paths through the traffic cop to see when
a plan will be a "one-shot" - planned and then executed immediately,
then discarded.I have already got plans for a significantly more sophisticated approach
to this.
Hi Tom,
I'd like to move forwards on this capability in this release cycle. I
want to be able to tell whether a plan is a one-shot plan, or not.
If you've got something planned here, please say what it is or
implement directly, so we can avoid me being late on later patches
that depend upon this.
In those cases we can take advantage of better optimisations. Most
interestingly, we can evaluate stable functions at plan time, to allow
us to handle partitioning and partial indexes better.I don't believe that's correct in detail.
If you can explain why you think this is wrong, I'm happy to remove
the line in evaluate_function() that says
if (funcform->provolatile == PROVOLATILE_STABLE && (context->estimate
|| context->oneshot))
then we're OK to evaluate the function immediately.
Thanks
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Simon Riggs <simon@2ndQuadrant.com> writes:
On Tue, Jun 14, 2011 at 9:36 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I have already got plans for a significantly more sophisticated approach
to this.
I'd like to move forwards on this capability in this release cycle. I
want to be able to tell whether a plan is a one-shot plan, or not.
If you've got something planned here, please say what it is or
implement directly, so we can avoid me being late on later patches
that depend upon this.
Yes, I'm planning to do something about this for 9.2, hopefully before
the next commitfest starts. See prior discussions --- what I have in
mind is to generate one-shot plans and test whether they're predicted to
be significantly cheaper than a generic plan. After a certain number of
failures to be better than generic, we'd give up and just use the
generic plan every time. Another heuristic that might be worth thinking
about is to not even bother with a generic plan until the N'th execution
of a prepared statement, for some N that's small but more than 1. We
already have that behavior for certain cases associated with particular
FE protocol usages, but not for plpgsql statements as an example.
I don't believe that's correct in detail.
If you can explain why you think this is wrong, I'm happy to remove
the line in evaluate_function() that says
I'm concerned about which snapshot the function is executed against.
regards, tom lane
On Mon, Aug 1, 2011 at 4:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Simon Riggs <simon@2ndQuadrant.com> writes:
On Tue, Jun 14, 2011 at 9:36 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I have already got plans for a significantly more sophisticated approach
to this.I'd like to move forwards on this capability in this release cycle. I
want to be able to tell whether a plan is a one-shot plan, or not.If you've got something planned here, please say what it is or
implement directly, so we can avoid me being late on later patches
that depend upon this.Yes, I'm planning to do something about this for 9.2, hopefully before
the next commitfest starts.
OK, I will work on the assumption that a "one shot plan" will be
visible in the output of the planner for 9.2.
See prior discussions --- what I have in
mind is to generate one-shot plans and test whether they're predicted to
be significantly cheaper than a generic plan. After a certain number of
failures to be better than generic, we'd give up and just use the
generic plan every time. Another heuristic that might be worth thinking
about is to not even bother with a generic plan until the N'th execution
of a prepared statement, for some N that's small but more than 1. We
already have that behavior for certain cases associated with particular
FE protocol usages, but not for plpgsql statements as an example.
One of the things I was looking at doing was allowing the operator
estimation functions mark the plan as "one-shot" if they used
non-uniform data to predict the estimate. That would require most
functions to observe the rule that if a plan is marked unsafe then
nobody marks it safe again later. More of a guideline, really.
For example, if we a doing a PK retrieval it will have a uniform
distribution and so we can always use the final plan, whereas a plan
that relates to a highly skewed distribution would be dangerous and so
would be marked one-shot.
This would almost eliminate the problem of parameters selected from a
skewed population or against a skewed distribution.
I'll leave that area to you if your looking to work there.
I don't believe that's correct in detail.
If you can explain why you think this is wrong, I'm happy to remove
the line in evaluate_function() that saysI'm concerned about which snapshot the function is executed against.
OK, I'll leave that for now and return to this thought later.
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Simon Riggs <simon@2ndQuadrant.com> writes:
One of the things I was looking at doing was allowing the operator
estimation functions mark the plan as "one-shot" if they used
non-uniform data to predict the estimate. That would require most
functions to observe the rule that if a plan is marked unsafe then
nobody marks it safe again later. More of a guideline, really.
For example, if we a doing a PK retrieval it will have a uniform
distribution and so we can always use the final plan, whereas a plan
that relates to a highly skewed distribution would be dangerous and so
would be marked one-shot.
I fail to detect the sanity in that. You seem to be confusing "skewed"
with "changing rapidly". There's no reason to assume that a nonuniform
distribution is less stable than one that is uniform, and in any case we
already invalidate all plans related to a table after any update of the
statistics by ANALYZE.
regards, tom lane
On Mon, Aug 1, 2011 at 6:39 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Simon Riggs <simon@2ndQuadrant.com> writes:
One of the things I was looking at doing was allowing the operator
estimation functions mark the plan as "one-shot" if they used
non-uniform data to predict the estimate. That would require most
functions to observe the rule that if a plan is marked unsafe then
nobody marks it safe again later. More of a guideline, really.For example, if we a doing a PK retrieval it will have a uniform
distribution and so we can always use the final plan, whereas a plan
that relates to a highly skewed distribution would be dangerous and so
would be marked one-shot.I fail to detect the sanity in that. You seem to be confusing "skewed"
with "changing rapidly". There's no reason to assume that a nonuniform
distribution is less stable than one that is uniform, and in any case we
already invalidate all plans related to a table after any update of the
statistics by ANALYZE.
Slightly missing each other, I feel.
SELECT * FROM bigtable WHERE skewcol = :param1
could have selectivity anywhere from 1.0 to 0.000000000000001 or
lower, though you don't know until you see the parameter.
Deciding the plan on the basis of a default value will frequently give
a bad plan.
What I would like to give people is "plan stability" without the need
to freeze plans or use hints. I would like us to recognise when the
selectivity result is potentially skewed and to avoid over-reliance on
such plans. If we address the cause of plan instability we need not
supply mechanisms higher up to cope with this.
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services