Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit e08d74c

Browse files
committed
Allow plan nodes with initPlans to be considered parallel-safe.
If the plan itself is parallel-safe, and the initPlans are too, there's no reason anymore to prevent the plan from being marked parallel-safe. That restriction (dating to commit ab77a5a) was really a special case of the fact that we couldn't transmit subplans to parallel workers at all. We fixed that in commit 5e6d8d2 and follow-ons, but this case never got addressed. We still forbid attaching initPlans to a Gather node that's inserted pursuant to debug_parallel_query = regress. That's because, when we hide the Gather from EXPLAIN output, we'd hide the initPlans too, causing cosmetic regression diffs. It seems inadvisable to kluge EXPLAIN to the extent required to make the output look the same, so just don't do it in that case. Along the way, this also takes care of some sloppiness about updating path costs to match when we move initplans from one place to another during createplan.c and setrefs.c. Since all the planning decisions are already made by that point, this is just cosmetic; but it seems good to keep EXPLAIN output consistent with where the initplans are. The diff in query_planner() might be worth remarking on. I found that one because after fixing things to allow parallel-safe initplans, one partition_prune test case changed plans (as shown in the patch) --- but only when debug_parallel_query was active. The reason proved to be that we only bothered to mark Result nodes as potentially parallel-safe when debug_parallel_query is on. This neglects the fact that parallel-safety may be of interest for a sub-query even though the Result itself doesn't parallelize. Discussion: https://postgr.es/m/1129530.1681317832@sss.pgh.pa.us
1 parent d0d4404 commit e08d74c

File tree

8 files changed

+169
-71
lines changed

8 files changed

+169
-71
lines changed

src/backend/optimizer/plan/createplan.c

+11-4
Original file line numberDiff line numberDiff line change
@@ -6481,27 +6481,34 @@ materialize_finished_plan(Plan *subplan)
64816481
{
64826482
Plan *matplan;
64836483
Path matpath; /* dummy for result of cost_material */
6484+
Cost initplan_cost;
6485+
bool unsafe_initplans;
64846486

64856487
matplan = (Plan *) make_material(subplan);
64866488

64876489
/*
64886490
* XXX horrid kluge: if there are any initPlans attached to the subplan,
64896491
* move them up to the Material node, which is now effectively the top
64906492
* plan node in its query level. This prevents failure in
6491-
* SS_finalize_plan(), which see for comments. We don't bother adjusting
6492-
* the subplan's cost estimate for this.
6493+
* SS_finalize_plan(), which see for comments.
64936494
*/
64946495
matplan->initPlan = subplan->initPlan;
64956496
subplan->initPlan = NIL;
64966497

6498+
/* Move the initplans' cost delta, as well */
6499+
SS_compute_initplan_cost(matplan->initPlan,
6500+
&initplan_cost, &unsafe_initplans);
6501+
subplan->startup_cost -= initplan_cost;
6502+
subplan->total_cost -= initplan_cost;
6503+
64976504
/* Set cost data */
64986505
cost_material(&matpath,
64996506
subplan->startup_cost,
65006507
subplan->total_cost,
65016508
subplan->plan_rows,
65026509
subplan->plan_width);
6503-
matplan->startup_cost = matpath.startup_cost;
6504-
matplan->total_cost = matpath.total_cost;
6510+
matplan->startup_cost = matpath.startup_cost + initplan_cost;
6511+
matplan->total_cost = matpath.total_cost + initplan_cost;
65056512
matplan->plan_rows = subplan->plan_rows;
65066513
matplan->plan_width = subplan->plan_width;
65076514
matplan->parallel_aware = false;

src/backend/optimizer/plan/planmain.c

+9-6
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,17 @@ query_planner(PlannerInfo *root,
112112
* quals are parallel-restricted. (We need not check
113113
* final_rel->reltarget because it's empty at this point.
114114
* Anything parallel-restricted in the query tlist will be
115-
* dealt with later.) This is normally pretty silly, because
116-
* a Result-only plan would never be interesting to
117-
* parallelize. However, if debug_parallel_query is on, then
118-
* we want to execute the Result in a parallel worker if
119-
* possible, so we must do this.
115+
* dealt with later.) We should always do this in a subquery,
116+
* since it might be useful to use the subquery in parallel
117+
* paths in the parent level. At top level this is normally
118+
* not worth the cycles, because a Result-only plan would
119+
* never be interesting to parallelize. However, if
120+
* debug_parallel_query is on, then we want to execute the
121+
* Result in a parallel worker if possible, so we must check.
120122
*/
121123
if (root->glob->parallelModeOK &&
122-
debug_parallel_query != DEBUG_PARALLEL_OFF)
124+
(root->query_level > 1 ||
125+
debug_parallel_query != DEBUG_PARALLEL_OFF))
123126
final_rel->consider_parallel =
124127
is_parallel_safe(root, parse->jointree->quals);
125128

src/backend/optimizer/plan/planner.c

+28-8
Original file line numberDiff line numberDiff line change
@@ -432,16 +432,23 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
432432
/*
433433
* Optionally add a Gather node for testing purposes, provided this is
434434
* actually a safe thing to do.
435-
*/
436-
if (debug_parallel_query != DEBUG_PARALLEL_OFF && top_plan->parallel_safe)
435+
*
436+
* We can add Gather even when top_plan has parallel-safe initPlans, but
437+
* then we have to move the initPlans to the Gather node because of
438+
* SS_finalize_plan's limitations. That would cause cosmetic breakage of
439+
* regression tests when debug_parallel_query = regress, because initPlans
440+
* that would normally appear on the top_plan move to the Gather, causing
441+
* them to disappear from EXPLAIN output. That doesn't seem worth kluging
442+
* EXPLAIN to hide, so skip it when debug_parallel_query = regress.
443+
*/
444+
if (debug_parallel_query != DEBUG_PARALLEL_OFF &&
445+
top_plan->parallel_safe &&
446+
(top_plan->initPlan == NIL ||
447+
debug_parallel_query != DEBUG_PARALLEL_REGRESS))
437448
{
438449
Gather *gather = makeNode(Gather);
439-
440-
/*
441-
* Top plan must not have any initPlans, else it shouldn't have been
442-
* marked parallel-safe.
443-
*/
444-
Assert(top_plan->initPlan == NIL);
450+
Cost initplan_cost;
451+
bool unsafe_initplans;
445452

446453
gather->plan.targetlist = top_plan->targetlist;
447454
gather->plan.qual = NIL;
@@ -451,6 +458,10 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
451458
gather->single_copy = true;
452459
gather->invisible = (debug_parallel_query == DEBUG_PARALLEL_REGRESS);
453460

461+
/* Transfer any initPlans to the new top node */
462+
gather->plan.initPlan = top_plan->initPlan;
463+
top_plan->initPlan = NIL;
464+
454465
/*
455466
* Since this Gather has no parallel-aware descendants to signal to,
456467
* we don't need a rescan Param.
@@ -470,6 +481,15 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
470481
gather->plan.parallel_aware = false;
471482
gather->plan.parallel_safe = false;
472483

484+
/*
485+
* Delete the initplans' cost from top_plan. We needn't add it to the
486+
* Gather node, since the above coding already included it there.
487+
*/
488+
SS_compute_initplan_cost(gather->plan.initPlan,
489+
&initplan_cost, &unsafe_initplans);
490+
top_plan->startup_cost -= initplan_cost;
491+
top_plan->total_cost -= initplan_cost;
492+
473493
/* use parallel mode for parallel plans. */
474494
root->glob->parallelModeNeeded = true;
475495

src/backend/optimizer/plan/setrefs.c

+22-10
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "optimizer/pathnode.h"
2424
#include "optimizer/planmain.h"
2525
#include "optimizer/planner.h"
26+
#include "optimizer/subselect.h"
2627
#include "optimizer/tlist.h"
2728
#include "parser/parse_relation.h"
2829
#include "tcop/utility.h"
@@ -1519,19 +1520,30 @@ clean_up_removed_plan_level(Plan *parent, Plan *child)
15191520
{
15201521
/*
15211522
* We have to be sure we don't lose any initplans, so move any that were
1522-
* attached to the parent plan to the child. If we do move any, the child
1523-
* is no longer parallel-safe.
1523+
* attached to the parent plan to the child. If any are parallel-unsafe,
1524+
* the child is no longer parallel-safe. As a cosmetic matter, also add
1525+
* the initplans' run costs to the child's costs.
15241526
*/
15251527
if (parent->initPlan)
1526-
child->parallel_safe = false;
1528+
{
1529+
Cost initplan_cost;
1530+
bool unsafe_initplans;
15271531

1528-
/*
1529-
* Attach plans this way so that parent's initplans are processed before
1530-
* any pre-existing initplans of the child. Probably doesn't matter, but
1531-
* let's preserve the ordering just in case.
1532-
*/
1533-
child->initPlan = list_concat(parent->initPlan,
1534-
child->initPlan);
1532+
SS_compute_initplan_cost(parent->initPlan,
1533+
&initplan_cost, &unsafe_initplans);
1534+
child->startup_cost += initplan_cost;
1535+
child->total_cost += initplan_cost;
1536+
if (unsafe_initplans)
1537+
child->parallel_safe = false;
1538+
1539+
/*
1540+
* Attach plans this way so that parent's initplans are processed
1541+
* before any pre-existing initplans of the child. Probably doesn't
1542+
* matter, but let's preserve the ordering just in case.
1543+
*/
1544+
child->initPlan = list_concat(parent->initPlan,
1545+
child->initPlan);
1546+
}
15351547

15361548
/*
15371549
* We also have to transfer the parent's column labeling info into the

src/backend/optimizer/plan/subselect.c

+65-19
Original file line numberDiff line numberDiff line change
@@ -1016,8 +1016,7 @@ SS_process_ctes(PlannerInfo *root)
10161016

10171017
/*
10181018
* CTE scans are not considered for parallelism (cf
1019-
* set_rel_consider_parallel), and even if they were, initPlans aren't
1020-
* parallel-safe.
1019+
* set_rel_consider_parallel).
10211020
*/
10221021
splan->parallel_safe = false;
10231022
splan->setParam = NIL;
@@ -2120,8 +2119,8 @@ SS_identify_outer_params(PlannerInfo *root)
21202119
* If any initPlans have been created in the current query level, they will
21212120
* get attached to the Plan tree created from whichever Path we select from
21222121
* the given rel. Increment all that rel's Paths' costs to account for them,
2123-
* and make sure the paths get marked as parallel-unsafe, since we can't
2124-
* currently transmit initPlans to parallel workers.
2122+
* and if any of the initPlans are parallel-unsafe, mark all the rel's Paths
2123+
* parallel-unsafe as well.
21252124
*
21262125
* This is separate from SS_attach_initplans because we might conditionally
21272126
* create more initPlans during create_plan(), depending on which Path we
@@ -2132,6 +2131,7 @@ void
21322131
SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
21332132
{
21342133
Cost initplan_cost;
2134+
bool unsafe_initplans;
21352135
ListCell *lc;
21362136

21372137
/* Nothing to do if no initPlans */
@@ -2140,17 +2140,10 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
21402140

21412141
/*
21422142
* Compute the cost increment just once, since it will be the same for all
2143-
* Paths. We assume each initPlan gets run once during top plan startup.
2144-
* This is a conservative overestimate, since in fact an initPlan might be
2145-
* executed later than plan startup, or even not at all.
2143+
* Paths. Also check for parallel-unsafe initPlans.
21462144
*/
2147-
initplan_cost = 0;
2148-
foreach(lc, root->init_plans)
2149-
{
2150-
SubPlan *initsubplan = (SubPlan *) lfirst(lc);
2151-
2152-
initplan_cost += initsubplan->startup_cost + initsubplan->per_call_cost;
2153-
}
2145+
SS_compute_initplan_cost(root->init_plans,
2146+
&initplan_cost, &unsafe_initplans);
21542147

21552148
/*
21562149
* Now adjust the costs and parallel_safe flags.
@@ -2161,19 +2154,71 @@ SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
21612154

21622155
path->startup_cost += initplan_cost;
21632156
path->total_cost += initplan_cost;
2164-
path->parallel_safe = false;
2157+
if (unsafe_initplans)
2158+
path->parallel_safe = false;
21652159
}
21662160

21672161
/*
2168-
* Forget about any partial paths and clear consider_parallel, too;
2169-
* they're not usable if we attached an initPlan.
2162+
* Adjust partial paths' costs too, or forget them entirely if we must
2163+
* consider the rel parallel-unsafe.
21702164
*/
2171-
final_rel->partial_pathlist = NIL;
2172-
final_rel->consider_parallel = false;
2165+
if (unsafe_initplans)
2166+
{
2167+
final_rel->partial_pathlist = NIL;
2168+
final_rel->consider_parallel = false;
2169+
}
2170+
else
2171+
{
2172+
foreach(lc, final_rel->partial_pathlist)
2173+
{
2174+
Path *path = (Path *) lfirst(lc);
2175+
2176+
path->startup_cost += initplan_cost;
2177+
path->total_cost += initplan_cost;
2178+
}
2179+
}
21732180

21742181
/* We needn't do set_cheapest() here, caller will do it */
21752182
}
21762183

2184+
/*
2185+
* SS_compute_initplan_cost - count up the cost delta for some initplans
2186+
*
2187+
* The total cost returned in *initplan_cost_p should be added to both the
2188+
* startup and total costs of the plan node the initplans get attached to.
2189+
* We also report whether any of the initplans are not parallel-safe.
2190+
*
2191+
* The primary user of this is SS_charge_for_initplans, but it's also
2192+
* used in adjusting costs when we move initplans to another plan node.
2193+
*/
2194+
void
2195+
SS_compute_initplan_cost(List *init_plans,
2196+
Cost *initplan_cost_p,
2197+
bool *unsafe_initplans_p)
2198+
{
2199+
Cost initplan_cost;
2200+
bool unsafe_initplans;
2201+
ListCell *lc;
2202+
2203+
/*
2204+
* We assume each initPlan gets run once during top plan startup. This is
2205+
* a conservative overestimate, since in fact an initPlan might be
2206+
* executed later than plan startup, or even not at all.
2207+
*/
2208+
initplan_cost = 0;
2209+
unsafe_initplans = false;
2210+
foreach(lc, init_plans)
2211+
{
2212+
SubPlan *initsubplan = lfirst_node(SubPlan, lc);
2213+
2214+
initplan_cost += initsubplan->startup_cost + initsubplan->per_call_cost;
2215+
if (!initsubplan->parallel_safe)
2216+
unsafe_initplans = true;
2217+
}
2218+
*initplan_cost_p = initplan_cost;
2219+
*unsafe_initplans_p = unsafe_initplans;
2220+
}
2221+
21772222
/*
21782223
* SS_attach_initplans - attach initplans to topmost plan node
21792224
*
@@ -2990,6 +3035,7 @@ SS_make_initplan_from_plan(PlannerInfo *root,
29903035
node->plan_id, prm->paramid);
29913036
get_first_col_type(plan, &node->firstColType, &node->firstColTypmod,
29923037
&node->firstColCollation);
3038+
node->parallel_safe = plan->parallel_safe;
29933039
node->setParam = list_make1_int(prm->paramid);
29943040

29953041
root->init_plans = lappend(root->init_plans, node);

src/backend/optimizer/util/pathnode.c

+15-3
Original file line numberDiff line numberDiff line change
@@ -3348,8 +3348,7 @@ create_minmaxagg_path(PlannerInfo *root,
33483348
/* For now, assume we are above any joins, so no parameterization */
33493349
pathnode->path.param_info = NULL;
33503350
pathnode->path.parallel_aware = false;
3351-
/* A MinMaxAggPath implies use of initplans, so cannot be parallel-safe */
3352-
pathnode->path.parallel_safe = false;
3351+
pathnode->path.parallel_safe = true; /* might change below */
33533352
pathnode->path.parallel_workers = 0;
33543353
/* Result is one unordered row */
33553354
pathnode->path.rows = 1;
@@ -3358,13 +3357,15 @@ create_minmaxagg_path(PlannerInfo *root,
33583357
pathnode->mmaggregates = mmaggregates;
33593358
pathnode->quals = quals;
33603359

3361-
/* Calculate cost of all the initplans ... */
3360+
/* Calculate cost of all the initplans, and check parallel safety */
33623361
initplan_cost = 0;
33633362
foreach(lc, mmaggregates)
33643363
{
33653364
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
33663365

33673366
initplan_cost += mminfo->pathcost;
3367+
if (!mminfo->path->parallel_safe)
3368+
pathnode->path.parallel_safe = false;
33683369
}
33693370

33703371
/* add tlist eval cost for each output row, plus cpu_tuple_cost */
@@ -3385,6 +3386,17 @@ create_minmaxagg_path(PlannerInfo *root,
33853386
pathnode->path.total_cost += qual_cost.startup + qual_cost.per_tuple;
33863387
}
33873388

3389+
/*
3390+
* If the initplans were all parallel-safe, also check safety of the
3391+
* target and quals. (The Result node itself isn't parallelizable, but if
3392+
* we are in a subquery then it can be useful for the outer query to know
3393+
* that this one is parallel-safe.)
3394+
*/
3395+
if (pathnode->path.parallel_safe)
3396+
pathnode->path.parallel_safe =
3397+
is_parallel_safe(root, (Node *) target->exprs) &&
3398+
is_parallel_safe(root, (Node *) quals);
3399+
33883400
return pathnode;
33893401
}
33903402

src/include/optimizer/subselect.h

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ extern Node *SS_replace_correlation_vars(PlannerInfo *root, Node *expr);
2828
extern Node *SS_process_sublinks(PlannerInfo *root, Node *expr, bool isQual);
2929
extern void SS_identify_outer_params(PlannerInfo *root);
3030
extern void SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel);
31+
extern void SS_compute_initplan_cost(List *init_plans,
32+
Cost *initplan_cost_p,
33+
bool *unsafe_initplans_p);
3134
extern void SS_attach_initplans(PlannerInfo *root, Plan *plan);
3235
extern void SS_finalize_plan(PlannerInfo *root, Plan *plan);
3336
extern Param *SS_make_initplan_output_param(PlannerInfo *root,

0 commit comments

Comments
 (0)