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->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 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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy