8000 Speed up planner's scanning for parallel-query hazards. · linearregression/postgres@da1c916 · GitHub
[go: up one dir, main page]

Skip to content

Commit da1c916

Browse files
committed
Speed up planner's scanning for parallel-query hazards.
We need to scan the whole parse tree for parallel-unsafe functions. If there are none, we'll later need to determine whether particular subtrees contain any parallel-restricted functions. The previous coding retained no knowledge from the first scan, even though this is very wasteful in the common case where the query contains only parallel-safe functions. We can bypass all of the later scans by remembering that fact. This provides a small but measurable speed improvement when the case applies, and shouldn't cost anything when it doesn't. Patch by me, reviewed by Robert Haas Discussion: <3740.1471538387@sss.pgh.pa.us>
1 parent 6f79ae7 commit da1c916

File tree

9 files changed

+133
-80
lines changed

9 files changed

+133
-80
lines changed

src/backend/nodes/outfuncs.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2029,6 +2029,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
20292029
WRITE_BOOL_FIELD(dependsOnRole);
20302030
WRITE_BOOL_FIELD(parallelModeOK);
20312031
WRITE_BOOL_FIELD(parallelModeNeeded);
2032+
WRITE_CHAR_FIELD(maxParallelHazard);
20322033
}
20332034

20342035
static void

src/backend/optimizer/path/allpaths.c

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ static void set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel,
7878
static void create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel);
7979
static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
8080
RangeTblEntry *rte);
81-
static bool function_rte_parallel_ok(RangeTblEntry *rte);
8281
static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
8382
RangeTblEntry *rte);
8483
static void set_tablesample_rel_size(PlannerInfo *root, RelOptInfo *rel,
@@ -542,8 +541,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
542541

543542
if (proparallel != PROPARALLEL_SAFE)
544543
return;
545-
if (has_parallel_hazard((Node *) rte->tablesample->args,
546-
false))
544+
if (!is_parallel_safe(root, (Node *) rte->tablesample->args))
547545
return;
548546
}
549547

@@ -596,7 +594,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
596594

597595
case RTE_FUNCTION:
598596
/* Check for parallel-restricted functions. */
599-
if (!function_rte_parallel_ok(rte))
597+
if (!is_parallel_safe(root, (Node *) rte->functions))
600598
return;
601599
break;
602600

@@ -629,40 +627,20 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
629627
* outer join clauses work correctly. It would likely break equivalence
630628
* classes, too.
631629
*/
632-
if (has_parallel_hazard((Node *) rel->baserestrictinfo, false))
630+
if (!is_parallel_safe(root, (Node *) rel->baserestrictinfo))
633631
return;
634632

635633
/*
636634
* Likewise, if the relation's outputs are not parallel-safe, give up.
637635
* (Usually, they're just Vars, but sometimes they're not.)
638636
*/
639-
if (has_parallel_hazard((Node *) rel-> F438 reltarget->exprs, false))
637+
if (!is_parallel_safe(root, (Node *) rel->reltarget->exprs))
640638
return;
641639

642640
/* We have a winner. */
643641
rel->consider_parallel = true;
644642
}
645643

646-
/*
647-
* Check whether a function RTE is scanning something parallel-restricted.
648-
*/
649-
static bool
650-
function_rte_parallel_ok(RangeTblEntry *rte)
651-
{
652-
ListCell *lc;
653-
654-
foreach(lc, rte->functions)
655-
{
656-
RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
657-
658-
Assert(IsA(rtfunc, RangeTblFunction));
659-
if (has_parallel_hazard(rtfunc->funcexpr, false))
660-
return false;
661-
}
662-
663-
return true;
664-
}
665-
666644
/*
667645
* set_plain_rel_pathlist
668646
* Build access paths for a plain relation (no subquery, no inheritance)

src/backend/optimizer/plan/planmain.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,13 @@ query_planner(PlannerInfo *root, List *tlist,
7171

7272
/*
7373
* If query allows parallelism in general, check whether the quals are
74-
* parallel-restricted. There's currently no real benefit to setting
75-
* this flag correctly because we can't yet reference subplans from
76-
* parallel workers. But that might change someday, so set this
77-
* correctly anyway.
74+
* parallel-restricted. (We need not check final_rel->reltarget
75+
* because it's empty at this point. Anything parallel-restricted in
76+
* the query tlist will be dealt with later.)
7877
*/
7978
if (root->glob->parallelModeOK)
8079
final_rel->consider_parallel =
81-
!has_parallel_hazard(parse->jointree->quals, false);
80+
is_parallel_safe(root, parse->jointree->quals);
8281

8382
/* The only path for it is a trivial Result path */
8483
add_path(final_rel, (Path *)

src/backend/optimizer/plan/planner.c

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "access/sysattr.h"
2424
#include "access/xact.h"
2525
#include "catalog/pg_constraint_fn.h"
26+
#include "catalog/pg_proc.h"
2627
#include "catalog/pg_type.h"
2728
#include "executor/executor.h"
2829
#include "executor/nodeAgg.h"
@@ -241,12 +242,26 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
241242
* time and execution time, so don't generate a parallel plan if we're in
242243
* serializable mode.
243244
*/
244-
glob->parallelModeOK = (cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
245-
IsUnderPostmaster && dynamic_shared_memory_type != DSM_IMPL_NONE &&
246-
parse->commandType == CMD_SELECT && !parse->hasModifyingCTE &&
247-
parse->utilityStmt == NULL && max_parallel_workers_per_gather > 0 &&
248-
!IsParallelWorker() && !IsolationIsSerializable() &&
249-
!has_parallel_hazard((Node *) parse, true);
245+
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
246+
IsUnderPostmaster &&
247+
dynamic_shared_memory_type != DSM_IMPL_NONE &&
248+
parse->commandType == CMD_SELECT &&
249+
parse->utilityStmt == NULL &&
250+
!parse->hasModifyingCTE &&
251+
max_parallel_workers_per_gather > 0 &&
252+
!IsParallelWorker() &&
253+
!IsolationIsSerializable())
254+
{
255+
/* all the cheap tests pass, so scan the query tree */
256+
glob->maxParallelHazard = max_parallel_hazard(parse);
257+
glob->parallelModeOK = (glob->maxParallelHazard != PROPARALLEL_UNSAFE);
258+
}
259+
else
260+
{
261+
/* skip the query tree scan, just assume it's unsafe */
262+
glob->maxParallelHazard = PROPARALLEL_UNSAFE;
263+
glob->parallelModeOK = false;
264+
}
250265

251266
/*
252267
* glob->parallelModeNeeded should tell us whether it's necessary to
@@ -1802,7 +1817,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
18021817
* computed by partial paths.
18031818
*/
18041819
if (current_rel->partial_pathlist &&
1805-
!has_parallel_hazard((Node *) scanjoin_target->exprs, false))
1820+
is_parallel_safe(root, (Node *) scanjoin_target->exprs))
18061821
{
18071822
/* Apply the scan/join target to each partial path */
18081823
foreach(lc, current_rel->partial_pathlist)
@@ -1948,8 +1963,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
19481963
* query.
19491964
*/
19501965
if (current_rel->consider_parallel &&
1951-
!has_parallel_hazard(parse->limitOffset, false) &&
1952-
!has_parallel_hazard(parse->limitCount, false))
1966+
is_parallel_safe(root, parse->limitOffset) &&
1967+
is_parallel_safe(root, parse->limitCount))
19531968
final_rel->consider_parallel = true;
19541969

19551970
/*
@@ -3326,8 +3341,8 @@ create_grouping_paths(PlannerInfo *root,
33263341
* target list and HAVING quals are parallel-safe.
33273342
*/
33283343
if (input_rel->consider_parallel &&
3329-
!has_parallel_hazard((Node *) target->exprs, false) &&
3330-
!has_parallel_hazard((Node *) parse->havingQual, false))
3344+
is_parallel_safe(root, (Node *) target->exprs) &&
3345+
is_parallel_safe(root, (Node *) parse->havingQual))
33313346
grouped_rel->consider_parallel = true;
33323347

33333348
/*
@@ -3881,8 +3896,8 @@ create_window_paths(PlannerInfo *root,
38813896
* target list and active windows for non-parallel-safe constructs.
38823897
*/
38833898
if (input_rel->consider_parallel &&
3884-
!has_parallel_hazard((Node *) output_target->exprs, false) &&
3885-
!has_parallel_hazard((Node *) activeWindows, false))
3899+
is_parallel_safe(root, (Node *) output_target->exprs) &&
3900+
is_parallel_safe(root, (Node *) activeWindows))
38863901
window_rel->consider_parallel = true;
38873902

38883903
/*
@@ -4272,7 +4287,7 @@ create_ordered_paths(PlannerInfo *root,
42724287
* target list is parallel-safe.
42734288
*/
42744289
if (input_rel->consider_parallel &&
4275-
!has_parallel_hazard((Node *) target->exprs, false))
4290+
is_parallel_safe(root, (Node *) target->exprs))
42764291
ordered_rel->consider_parallel = true;
42774292

42784293
/*

src/backend/optimizer/util/clauses.c

Lines changed: 86 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,9 @@ typedef struct
9191

9292
typedef struct
9393
{
94-
bool allow_restricted;
95-
} has_parallel_hazard_arg;
94+
char max_hazard; /* worst proparallel hazard found so far */
95+
char max_interesting; /* worst proparallel hazard of interest */
96+
} max_parallel_hazard_context;
9697

9798
static bool contain_agg_clause_walker(Node *node, void *context);
9899
static bool get_agg_clause_costs_walker(Node *node,
@@ -103,8 +104,8 @@ static bool contain_subplans_walker(Node *node, void *context);
103104
static bool contain_mutable_functions_walker(Node *node, void *context);
104105
static bool contain_volatile_functions_walker(Node *node, void *context);
105106
static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
106-
static bool has_parallel_hazard_walker(Node *node,
107-
has_parallel_hazard_arg *context);
107+
static bool max_parallel_hazard_walker(Node *node,
108+
max_parallel_hazard_context *context);
108109
static bool contain_nonstrict_functions_walker(Node *node, void *context);
109110
static bool contain_context_dependent_node(Node *clause);
110111
static bool contain_context_dependent_node_walker(Node *node, int *flags);
@@ -1100,46 +1101,98 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
11001101
context);
11011102
}
11021103

1104+
11031105
/*****************************************************************************
11041106
* Check queries for parallel unsafe and/or restricted constructs
11051107
*****************************************************************************/
11061108

11071109
/*
1108-
* Check whether a node tree contains parallel hazards. This is used both on
1109-
* the entire query tree, to see whether the query can be parallelized at all
1110-
* (with allow_restricted = true), and also to evaluate whether a particular
1111-
* expression is safe to run within a parallel worker (with allow_restricted =
1112-
* false). We could separate these concerns into two different functions, but
1113-
* there's enough overlap that it doesn't seem worthwhile.
1110+
* max_parallel_hazard
1111+
* Find the worst parallel-hazard level in the given query
1112+
*
1113+
* Returns the worst function hazard property (the earliest in this list:
1114+
* PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
1115+
* be found in the given parsetree. We use this to find out whether the query
1116+
* can be parallelized at all. The caller will also save the result in
1117+
* PlannerGlobal so as to short-circuit checks of portions of the querytree
1118+
* later, in the common case where everything is SAFE.
1119+
*/
1120+
char
1121+
max_parallel_hazard(Query *parse)
1122+
{
1123+
max_parallel_hazard_context context;
1124+
1125+
context.max_hazard = PROPARALLEL_SAFE;
1126+
context.max_interesting = PROPARALLEL_UNSAFE;
1127+
(void) max_parallel_hazard_walker((Node *) parse, &context);
1128+
return context.max_hazard;
1129+
}
1130+
1131+
/*
1132+
* is_parallel_safe
1133+
* Detect whether the given expr contains only parallel-safe functions
1134+
*
1135+
* root->glob->maxParallelHazard must previously have been set to the
1136+
* result of max_parallel_hazard() on the whole query.
11141137
*/
11151138
bool
1116-
has_parallel_hazard(Node *node, bool allow_restricted)
1139+
is_parallel_safe(PlannerInfo *root, Node *node)
11171140
{
1118-
has_parallel_hazard_arg context;
1141+
max_parallel_hazard_context context;
11191142

1120-
context.allow_restricted = allow_restricted;
1121-
return has_parallel_hazard_walker(node, &context);
1143+
/* If max_parallel_hazard found nothing unsafe, we don't need to look */
1144+
if (root->glob->maxParallelHazard == PROPARALLEL_SAFE)
1145+
return true;
1146+
/* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
1147+
context.max_hazard = PROPARALLEL_SAFE;
1148+
context.max_interesting = PROPARALLEL_RESTRICTED;
1149+
return !max_parallel_hazard_walker(node, &context);
11221150
}
11231151

1152+
/* core logic for all parallel-hazard checks */
11241153
static bool
1125-
has_parallel_hazard_checker(Oid func_id, void *context)
1154+
max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
11261155
{
1127-
char proparallel = func_parallel(func_id);
1156+
switch (proparallel)
1157+
{
1158+
case PROPARALLEL_SAFE:
1159+
/* nothing to see here, move along */
1160+
break;
1161+
case PROPARALLEL_RESTRICTED:
1162+
/* increase max_hazard to RESTRICTED */
1163+
Assert(context->max_hazard != PROPARALLEL_UNSAFE);
1164+
context->max_hazard = proparallel;
1165+
/* done if we are C2EE not expecting any unsafe functions */
1166+
if (context->max_interesting == proparallel)
1167+
return true;
1168+
break;
1169+
case PROPARALLEL_UNSAFE:
1170+
context->max_hazard = proparallel;
1171+
/* we're always done at the first unsafe construct */
1172+
return true;
1173+
default:
1174+
elog(ERROR, "unrecognized proparallel value \"%c\"", proparallel);
1175+
break;
1176+
}
1177+
return false;
1178+
}
11281179

1129-
if (((has_parallel_hazard_arg *) context)->allow_restricted)
1130-
return (proparallel == PROPARALLEL_UNSAFE);
1131-
else
1132-
return (proparallel != PROPARALLEL_SAFE);
1180+
/* check_functions_in_node callback */
1181+
static bool
1182+
max_parallel_hazard_checker(Oid func_id, void *context)
1183+
{
1184+
return max_parallel_hazard_test(func_parallel(func_id),
1185+
(max_parallel_hazard_context *) context);
11331186
}
11341187

11351188
static bool
1136-
has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
1189+
max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
11371190
{
11381191
if (node == NULL)
11391192
return false;
11401193

11411194
/* Check for hazardous functions in node itself */
1142-
if (check_functions_in_node(node, has_parallel_hazard_checker,
1195+
if (check_functions_in_node(node, max_parallel_hazard_checker,
11431196
context))
11441197
return true;
11451198

@@ -1156,7 +1209,7 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
11561209
*/
11571210
if (IsA(node, CoerceToDomain))
11581211
{
1159-
if (!context->allow_restricted)
1212+
if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
11601213
return true;
11611214
}
11621215

@@ -1167,7 +1220,7 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
11671220
{
11681221
RestrictInfo *rinfo = (RestrictInfo *) node;
11691222

1170-
return has_parallel_hazard_walker((Node *) rinfo->clause, context);
1223+
return max_parallel_hazard_walker((Node *) rinfo->clause, context);
11711224
}
11721225

11731226
/*
@@ -1176,13 +1229,13 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
11761229
* not worry about examining their contents; if they are unsafe, we would
11771230
* have found that out while examining the whole tree before reduction of
11781231
* sublinks to subplans. (Really we should not see SubLink during a
1179-
* not-allow_restricted scan, but if we do, return true.)
1232+
* max_interesting == restricted scan, but if we do, return true.)
11801233
*/
11811234
else if (IsA(node, SubLink) ||
11821235
IsA(node, SubPlan) ||
11831236
IsA(node, AlternativeSubPlan))
11841237
{
1185-
if (!context->allow_restricted)
1238+
if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
11861239
return true;
11871240
}
11881241

@@ -1192,7 +1245,7 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
11921245
*/
11931246
else if (IsA(node, Param))
11941247
{
1195-
if (!context->allow_restricted)
1248+
if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
11961249
return true;
11971250
}
11981251

@@ -1207,20 +1260,24 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
12071260

12081261
/* SELECT FOR UPDATE/SHARE must be treated as unsafe */
12091262
if (query->rowMarks != NULL)
1263+
{
1264+
context->max_hazard = PROPARALLEL_UNSAFE;
12101265
return true;
1266+
}
12111267

12121268
/* Recurse into subselects */
12131269
return query_tree_walker(query,
1214-
has_parallel_hazard_walker,
1270+
max_parallel_hazard_walker,
12151271
context, 0);
12161272
}
12171273

12181274
/* Recurse to check arguments */
12191275
return expression_tree_walker(node,
1220-
has_parallel_hazard_walker,
1276+
max_parallel_hazard_walker,
12211277
context);
12221278
}
12231279

1280+
12241281
/*****************************************************************************
12251282
* Check clauses for nonstrict functions
12261283
*****************************************************************************/

0 commit comments

Comments
 (0)
0