aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/optimizer/plan/createplan.c15
-rw-r--r--src/backend/optimizer/plan/planmain.c15
-rw-r--r--src/backend/optimizer/plan/planner.c36
-rw-r--r--src/backend/optimizer/plan/setrefs.c32
-rw-r--r--src/backend/optimizer/plan/subselect.c84
-rw-r--r--src/backend/optimizer/util/pathnode.c18
-rw-r--r--src/include/optimizer/subselect.h3
-rw-r--r--src/test/regress/expected/partition_prune.out37
8 files changed, 169 insertions, 71 deletions
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ec73789bc21..af48109058d 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6481,6 +6481,8 @@ materialize_finished_plan(Plan *subplan)
{
Plan *matplan;
Path matpath; /* dummy for result of cost_material */
+ Cost initplan_cost;
+ bool unsafe_initplans;
matplan = (Plan *) make_material(subplan);
@@ -6488,20 +6490,25 @@ materialize_finished_plan(Plan *subplan)
* XXX horrid kluge: if there are any initPlans attached to the subplan,
* move them up to the Material node, which is now effectively the top
* plan node in its query level. This prevents failure in
- * SS_finalize_plan(), which see for comments. We don't bother adjusting
- * the subplan's cost estimate for this.
+ * SS_finalize_plan(), which see for comments.
*/
matplan->initPlan = subplan->initPlan;
subplan->initPlan = NIL;
+ /* Move the initplans' cost delta, as well */
+ SS_compute_initplan_cost(matplan->initPlan,
+ &initplan_cost, &unsafe_initplans);
+ subplan->startup_cost -= initplan_cost;
+ subplan->total_cost -= initplan_cost;
+
/* Set cost data */
cost_material(&matpath,
subplan->startup_cost,
subplan->total_cost,
subplan->plan_rows,
subplan->plan_width);
- matplan->startup_cost = matpath.startup_cost;
- matplan->total_cost = matpath.total_cost;
+ matplan->startup_cost = matpath.startup_cost + initplan_cost;
+ matplan->total_cost = matpath.total_cost + initplan_cost;
matplan->plan_rows = subplan->plan_rows;
matplan->plan_width = subplan->plan_width;
matplan->parallel_aware = false;
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 7afd434c601..fcc0eacd253 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -112,14 +112,17 @@ query_planner(PlannerInfo *root,
* quals are parallel-restricted. (We need not check
* final_rel->reltarget because it's empty at this point.
* Anything parallel-restricted in the query tlist will be
- * dealt with later.) This is normally pretty silly, because
- * a Result-only plan would never be interesting to
- * parallelize. However, if debug_parallel_query is on, then
- * we want to execute the Result in a parallel worker if
- * possible, so we must do this.
+ * dealt with later.) We should always do this in a subquery,
+ * since it might be useful to use the subquery in parallel
+ * paths in the parent level. At top level this is normally
+ * not worth the cycles, because a Result-only plan would
+ * never be interesting to parallelize. However, if
+ * debug_parallel_query is on, then we want to execute the
+ * Result in a parallel worker if possible, so we must check.
*/
if (root->glob->parallelModeOK &&
- debug_parallel_query != DEBUG_PARALLEL_OFF)
+ (root->query_level > 1 ||
+ debug_parallel_query != DEBUG_PARALLEL_OFF))
final_rel->consider_parallel =
is_parallel_safe(root, parse->jointree->quals);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 0e12fdeb602..44efb1f4ebc 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -432,16 +432,23 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Optionally add a Gather node for testing purposes, provided this is
* actually a safe thing to do.
- */
- if (debug_parallel_query != DEBUG_PARALLEL_OFF && top_plan->parallel_safe)
+ *
+ * We can add Gather even when top_plan has parallel-safe initPlans, but
+ * then we have to move the initPlans to the Gather node because of
+ * SS_finalize_plan's limitations. That would cause cosmetic breakage of
+ * regression tests when debug_parallel_query = regress, because initPlans
+ * that would normally appear on the top_plan move to the Gather, causing
+ * them to disappear from EXPLAIN output. That doesn't seem worth kluging
+ * EXPLAIN to hide, so skip it when debug_parallel_query = regress.
+ */
+ if (debug_parallel_query != DEBUG_PARALLEL_OFF &&
+ top_plan->parallel_safe &&
+ (top_plan->initPlan == NIL ||
+ debug_parallel_query != DEBUG_PARALLEL_REGRESS))
{
Gather *gather = makeNode(Gather);
-
- /*
- * Top plan must not have any initPlans, else it shouldn't have been
- * marked parallel-safe.
- */
- Assert(top_plan->initPlan == NIL);
+ Cost initplan_cost;
+ bool unsafe_initplans;
gather->plan.targetlist = top_plan->targetlist;
gather->plan.qual = NIL;
@@ -451,6 +458,10 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
gather->single_copy = true;
gather->invisible = (debug_parallel_query == DEBUG_PARALLEL_REGRESS);
+ /* Transfer any initPlans to the new top node */
+ gather->plan.initPlan = top_plan->initPlan;
+ top_plan->initPlan = NIL;
+
/*
* Since this Gather has no parallel-aware descendants to signal to,
* we don't need a rescan Param.
@@ -470,6 +481,15 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
gather->plan.parallel_aware = false;
gather->plan.parallel_safe = false;
+ /*
+ * Delete the initplans' cost from top_plan. We needn't add it to the
+ * Gather node, since the above coding already included it there.
+ */
+ SS_compute_initplan_cost(gather->plan.initPlan,
+ &initplan_cost, &unsafe_initplans);
+ top_plan->startup_cost -= initplan_cost;
+ top_plan->total_cost -= initplan_cost;
+
/* use parallel mode for parallel plans. */
root->glob->parallelModeNeeded = true;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 16e5537f7f9..97fa561e4ea 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -23,6 +23,7 @@
#include "optimizer/pathnode.h"
#include "optimizer/planmain.h"
#include "optimizer/planner.h"
+#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "parser/parse_relation.h"
#include "tcop/utility.h"
@@ -1519,19 +1520,30 @@ clean_up_removed_plan_level(Plan *parent, Plan *child)
{
/*
* We have to be sure we don't lose any initplans, so move any that were
- * attached to the parent plan to the child. If we do move any, the child
- * is no longer parallel-safe.
+ * attached to the parent plan to the child. If any are parallel-unsafe,
+ * the child is no longer parallel-safe. As a cosmetic matter, also add
+ * the initplans' run costs to the child's costs.
*/
if (parent->initPlan)
- child->parallel_safe = false;
+ {
+ Cost initplan_cost;
+ bool unsafe_initplans;
- /*
- * Attach plans this way so that parent's initplans are processed before
- * any pre-existing initplans of the child. Probably doesn't matter, but
- * let's preserve the ordering just in case.
- */
- child->initPlan = list_concat(parent->initPlan,
- child->initPlan);
+ SS_compute_initplan_cost(parent->initPlan,
+ &initplan_cost, &unsafe_initplans);
+ child->startup_cost += initplan_cost;
+ child->total_cost += initplan_cost;
+ if (unsafe_initplans)
+ child->parallel_safe = false;
+
+ /*
+ * Attach plans this way so that parent's initplans are processed
+ * before any pre-existing initplans of the child. Probably doesn't
+ * matter, but let's preserve the ordering just in case.
+ */
+ child->initPlan = list_concat(parent->initPlan,
+ child->initPlan);
+ }
/*
* We also have to transfer the parent's column labeling info into the
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 250ba8edcb8..7a9fe88fec3 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1016,8 +1016,7 @@ SS_process_ctes(PlannerInfo *root)
/*
* CTE scans are not considered for parallelism (cf
- * set_rel_consider_parallel), and even if they were, initPlans aren't
- * parallel-safe.
+ * set_rel_consider_parallel).
*/
splan->parallel_safe = false;
splan->setParam = NIL;
@@ -2120,8 +2119,8 @@ SS_identify_outer_params(PlannerInfo *root)
* If any initPlans have been created in the current query level, they will
* get attached to the Plan tree created from whichever Path we select from
* the given rel. Increment all that rel's Paths' costs to account for them,
- * and make sure the paths get marked as parallel-unsafe, since we can't
- * currently transmit initPlans to parallel workers.
+ * and if any of the initPlans are parallel-unsafe, mark all the rel's Paths
+ * parallel-unsafe as well.
*
* This is separate from SS_attach_initplans because we might conditionally
* create more initPlans during create_plan(), depending on which Path we
@@ -2132,6 +2131,7 @@ void
SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
{
Cost initplan_cost;
+ bool unsafe_initplans;
ListCell *lc;
/* Nothing to do if no initPlans */
@@ -2140,17 +2140,10 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
/*
* Compute the cost increment just once, since it will be the same for all
- * Paths. We assume each initPlan gets run once during top plan startup.
- * This is a conservative overestimate, since in fact an initPlan might be
- * executed later than plan startup, or even not at all.
+ * Paths. Also check for parallel-unsafe initPlans.
*/
- initplan_cost = 0;
- foreach(lc, root->init_plans)
- {
- SubPlan *initsubplan = (SubPlan *) lfirst(lc);
-
- initplan_cost += initsubplan->startup_cost + initsubplan->per_call_cost;
- }
+ SS_compute_initplan_cost(root->init_plans,
+ &initplan_cost, &unsafe_initplans);
/*
* Now adjust the costs and parallel_safe flags.
@@ -2161,20 +2154,72 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
path->startup_cost += initplan_cost;
path->total_cost += initplan_cost;
- path->parallel_safe = false;
+ if (unsafe_initplans)
+ path->parallel_safe = false;
}
/*
- * Forget about any partial paths and clear consider_parallel, too;
- * they're not usable if we attached an initPlan.
+ * Adjust partial paths' costs too, or forget them entirely if we must
+ * consider the rel parallel-unsafe.
*/
- final_rel->partial_pathlist = NIL;
- final_rel->consider_parallel = false;
+ if (unsafe_initplans)
+ {
+ final_rel->partial_pathlist = NIL;
+ final_rel->consider_parallel = false;
+ }
+ else
+ {
+ foreach(lc, final_rel->partial_pathlist)
+ {
+ Path *path = (Path *) lfirst(lc);
+
+ path->startup_cost += initplan_cost;
+ path->total_cost += initplan_cost;
+ }
+ }
/* We needn't do set_cheapest() here, caller will do it */
}
/*
+ * SS_compute_initplan_cost - count up the cost delta for some initplans
+ *
+ * The total cost returned in *initplan_cost_p should be added to both the
+ * startup and total costs of the plan node the initplans get attached to.
+ * We also report whether any of the initplans are not parallel-safe.
+ *
+ * The primary user of this is SS_charge_for_initplans, but it's also
+ * used in adjusting costs when we move initplans to another plan node.
+ */
+void
+SS_compute_initplan_cost(List *init_plans,
+ Cost *initplan_cost_p,
+ bool *unsafe_initplans_p)
+{
+ Cost initplan_cost;
+ bool unsafe_initplans;
+ ListCell *lc;
+
+ /*
+ * We assume each initPlan gets run once during top plan startup. This is
+ * a conservative overestimate, since in fact an initPlan might be
+ * executed later than plan startup, or even not at all.
+ */
+ initplan_cost = 0;
+ unsafe_initplans = false;
+ foreach(lc, init_plans)
+ {
+ SubPlan *initsubplan = lfirst_node(SubPlan, lc);
+
+ initplan_cost += initsubplan->startup_cost + initsubplan->per_call_cost;
+ if (!initsubplan->parallel_safe)
+ unsafe_initplans = true;
+ }
+ *initplan_cost_p = initplan_cost;
+ *unsafe_initplans_p = unsafe_initplans;
+}
+
+/*
* SS_attach_initplans - attach initplans to topmost plan node
*
* Attach any initplans created in the current query level to the specified
@@ -2990,6 +3035,7 @@ SS_make_initplan_from_plan(PlannerInfo *root,
node->plan_id, prm->paramid);
get_first_col_type(plan, &node->firstColType, &node->firstColTypmod,
&node->firstColCollation);
+ node->parallel_safe = plan->parallel_safe;
node->setParam = list_make1_int(prm->paramid);
root->init_plans = lappend(root->init_plans, node);
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 5f5596841c8..f123fcb41e3 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3348,8 +3348,7 @@ create_minmaxagg_path(PlannerInfo *root,
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
- /* A MinMaxAggPath implies use of initplans, so cannot be parallel-safe */
- pathnode->path.parallel_safe = false;
+ pathnode->path.parallel_safe = true; /* might change below */
pathnode->path.parallel_workers = 0;
/* Result is one unordered row */
pathnode->path.rows = 1;
@@ -3358,13 +3357,15 @@ create_minmaxagg_path(PlannerInfo *root,
pathnode->mmaggregates = mmaggregates;
pathnode->quals = quals;
- /* Calculate cost of all the initplans ... */
+ /* Calculate cost of all the initplans, and check parallel safety */
initplan_cost = 0;
foreach(lc, mmaggregates)
{
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
initplan_cost += mminfo->pathcost;
+ if (!mminfo->path->parallel_safe)
+ pathnode->path.parallel_safe = false;
}
/* add tlist eval cost for each output row, plus cpu_tuple_cost */
@@ -3385,6 +3386,17 @@ create_minmaxagg_path(PlannerInfo *root,
pathnode->path.total_cost += qual_cost.startup + qual_cost.per_tuple;
}
+ /*
+ * If the initplans were all parallel-safe, also check safety of the
+ * target and quals. (The Result node itself isn't parallelizable, but if
+ * we are in a subquery then it can be useful for the outer query to know
+ * that this one is parallel-safe.)
+ */
+ if (pathnode->path.parallel_safe)
+ pathnode->path.parallel_safe =
+ is_parallel_safe(root, (Node *) target->exprs) &&
+ is_parallel_safe(root, (Node *) quals);
+
return pathnode;
}
diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h
index c03ffc56bf6..44bc0bda7e9 100644
--- a/src/include/optimizer/subselect.h
+++ b/src/include/optimizer/subselect.h
@@ -28,6 +28,9 @@ extern Node *SS_replace_correlation_vars(PlannerInfo *root, Node *expr);
extern Node *SS_process_sublinks(PlannerInfo *root, Node *expr, bool isQual);
extern void SS_identify_outer_params(PlannerInfo *root);
extern void SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel);
+extern void SS_compute_initplan_cost(List *init_plans,
+ Cost *initplan_cost_p,
+ bool *unsafe_initplans_p);
extern void SS_attach_initplans(PlannerInfo *root, Plan *plan);
extern void SS_finalize_plan(PlannerInfo *root, Plan *plan);
extern Param *SS_make_initplan_output_param(PlannerInfo *root,
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 2abf7593858..1eb347503aa 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -3903,30 +3903,25 @@ select explain_parallel_append(
select * from listp where a = (select 2);');
explain_parallel_append
-----------------------------------------------------------------------------------
- Append (actual rows=N loops=N)
- -> Gather (actual rows=N loops=N)
- Workers Planned: 2
- Params Evaluated: $0
- Workers Launched: N
- InitPlan 1 (returns $0)
- -> Result (actual rows=N loops=N)
- -> Parallel Append (actual rows=N loops=N)
- -> Seq Scan on listp_12_1 listp_1 (actual rows=N loops=N)
- Filter: (a = $0)
- -> Parallel Seq Scan on listp_12_2 listp_2 (never executed)
- Filter: (a = $0)
- -> Gather (actual rows=N loops=N)
- Workers Planned: 2
- Params Evaluated: $1
- Workers Launched: N
- InitPlan 2 (returns $1)
- -> Result (actual rows=N loops=N)
+ Gather (actual rows=N loops=N)
+ Workers Planned: 2
+ Workers Launched: N
+ -> Parallel Append (actual rows=N loops=N)
-> Parallel Append (actual rows=N loops=N)
- -> Seq Scan on listp_12_1 listp_4 (never executed)
+ InitPlan 2 (returns $1)
+ -> Result (actual rows=N loops=N)
+ -> Seq Scan on listp_12_1 listp_1 (never executed)
Filter: (a = $1)
- -> Parallel Seq Scan on listp_12_2 listp_5 (actual rows=N loops=N)
+ -> Parallel Seq Scan on listp_12_2 listp_2 (actual rows=N loops=N)
Filter: (a = $1)
-(23 rows)
+ -> Parallel Append (actual rows=N loops=N)
+ InitPlan 1 (returns $0)
+ -> Result (actual rows=N loops=N)
+ -> Seq Scan on listp_12_1 listp_4 (actual rows=N loops=N)
+ Filter: (a = $0)
+ -> Parallel Seq Scan on listp_12_2 listp_5 (never executed)
+ Filter: (a = $0)
+(18 rows)
drop table listp;
reset parallel_tuple_cost;