Speed up planner's scanning for parallel-query hazards.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 19 Aug 2016 18:03:07 +0000 (14:03 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 19 Aug 2016 18:03:13 +0000 (14:03 -0400)
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>

src/backend/nodes/outfuncs.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/plan/planmain.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/util/clauses.c
src/backend/optimizer/util/pathnode.c
src/backend/optimizer/util/relnode.c
src/include/nodes/relation.h
src/include/optimizer/clauses.h

index 1fab807772cc189970bd62c4b184b772f9f2fa3b..50019f4164fbbaf625fc27dbe0b0cb7f3c73f4e5 100644 (file)
@@ -2029,6 +2029,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
        WRITE_BOOL_FIELD(dependsOnRole);
        WRITE_BOOL_FIELD(parallelModeOK);
        WRITE_BOOL_FIELD(parallelModeNeeded);
+       WRITE_CHAR_FIELD(maxParallelHazard);
 }
 
 static void
index 88d833a2e89682b12eee1c4b1c50a4e7f47add1a..af73792227d02f4529fd4f8b3aedc125d7caf1bc 100644 (file)
@@ -78,7 +78,6 @@ static void set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel,
 static void create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel);
 static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
                                                  RangeTblEntry *rte);
-static bool function_rte_parallel_ok(RangeTblEntry *rte);
 static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
                                           RangeTblEntry *rte);
 static void set_tablesample_rel_size(PlannerInfo *root, RelOptInfo *rel,
@@ -542,8 +541,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 
                                if (proparallel != PROPARALLEL_SAFE)
                                        return;
-                               if (has_parallel_hazard((Node *) rte->tablesample->args,
-                                                                               false))
+                               if (!is_parallel_safe(root, (Node *) rte->tablesample->args))
                                        return;
                        }
 
@@ -596,7 +594,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 
                case RTE_FUNCTION:
                        /* Check for parallel-restricted functions. */
-                       if (!function_rte_parallel_ok(rte))
+                       if (!is_parallel_safe(root, (Node *) rte->functions))
                                return;
                        break;
 
@@ -629,40 +627,20 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
         * outer join clauses work correctly.  It would likely break equivalence
         * classes, too.
         */
-       if (has_parallel_hazard((Node *) rel->baserestrictinfo, false))
+       if (!is_parallel_safe(root, (Node *) rel->baserestrictinfo))
                return;
 
        /*
         * Likewise, if the relation's outputs are not parallel-safe, give up.
         * (Usually, they're just Vars, but sometimes they're not.)
         */
-       if (has_parallel_hazard((Node *) rel->reltarget->exprs, false))
+       if (!is_parallel_safe(root, (Node *) rel->reltarget->exprs))
                return;
 
        /* We have a winner. */
        rel->consider_parallel = true;
 }
 
-/*
- * Check whether a function RTE is scanning something parallel-restricted.
- */
-static bool
-function_rte_parallel_ok(RangeTblEntry *rte)
-{
-       ListCell   *lc;
-
-       foreach(lc, rte->functions)
-       {
-               RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
-
-               Assert(IsA(rtfunc, RangeTblFunction));
-               if (has_parallel_hazard(rtfunc->funcexpr, false))
-                       return false;
-       }
-
-       return true;
-}
-
 /*
  * set_plain_rel_pathlist
  *       Build access paths for a plain relation (no subquery, no inheritance)
index 27234ffa22468c0d353fb74ab1d3bf5aa02538fb..e7ae7ae8ae7db6fd258438f5c241df4523c5faba 100644 (file)
@@ -71,14 +71,13 @@ query_planner(PlannerInfo *root, List *tlist,
 
                /*
                 * If query allows parallelism in general, check whether the quals are
-                * parallel-restricted.  There's currently no real benefit to setting
-                * this flag correctly because we can't yet reference subplans from
-                * parallel workers.  But that might change someday, so set this
-                * correctly anyway.
+                * 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.)
                 */
                if (root->glob->parallelModeOK)
                        final_rel->consider_parallel =
-                               !has_parallel_hazard(parse->jointree->quals, false);
+                               is_parallel_safe(root, parse->jointree->quals);
 
                /* The only path for it is a trivial Result path */
                add_path(final_rel, (Path *)
index b265628325146d71d4e4e8f71ce84e37614d9f52..174210be6cf26a880aa1be30f55a889d248b7d10 100644 (file)
@@ -23,6 +23,7 @@
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/pg_constraint_fn.h"
+#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "executor/nodeAgg.h"
@@ -241,12 +242,26 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
         * time and execution time, so don't generate a parallel plan if we're in
         * serializable mode.
         */
-       glob->parallelModeOK = (cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
-               IsUnderPostmaster && dynamic_shared_memory_type != DSM_IMPL_NONE &&
-               parse->commandType == CMD_SELECT && !parse->hasModifyingCTE &&
-               parse->utilityStmt == NULL && max_parallel_workers_per_gather > 0 &&
-               !IsParallelWorker() && !IsolationIsSerializable() &&
-               !has_parallel_hazard((Node *) parse, true);
+       if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
+               IsUnderPostmaster &&
+               dynamic_shared_memory_type != DSM_IMPL_NONE &&
+               parse->commandType == CMD_SELECT &&
+               parse->utilityStmt == NULL &&
+               !parse->hasModifyingCTE &&
+               max_parallel_workers_per_gather > 0 &&
+               !IsParallelWorker() &&
+               !IsolationIsSerializable())
+       {
+               /* all the cheap tests pass, so scan the query tree */
+               glob->maxParallelHazard = max_parallel_hazard(parse);
+               glob->parallelModeOK = (glob->maxParallelHazard != PROPARALLEL_UNSAFE);
+       }
+       else
+       {
+               /* skip the query tree scan, just assume it's unsafe */
+               glob->maxParallelHazard = PROPARALLEL_UNSAFE;
+               glob->parallelModeOK = false;
+       }
 
        /*
         * glob->parallelModeNeeded should tell us whether it's necessary to
@@ -1802,7 +1817,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
                 * computed by partial paths.
                 */
                if (current_rel->partial_pathlist &&
-                       !has_parallel_hazard((Node *) scanjoin_target->exprs, false))
+                       is_parallel_safe(root, (Node *) scanjoin_target->exprs))
                {
                        /* Apply the scan/join target to each partial path */
                        foreach(lc, current_rel->partial_pathlist)
@@ -1948,8 +1963,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
         * query.
         */
        if (current_rel->consider_parallel &&
-               !has_parallel_hazard(parse->limitOffset, false) &&
-               !has_parallel_hazard(parse->limitCount, false))
+               is_parallel_safe(root, parse->limitOffset) &&
+               is_parallel_safe(root, parse->limitCount))
                final_rel->consider_parallel = true;
 
        /*
@@ -3326,8 +3341,8 @@ create_grouping_paths(PlannerInfo *root,
         * target list and HAVING quals are parallel-safe.
         */
        if (input_rel->consider_parallel &&
-               !has_parallel_hazard((Node *) target->exprs, false) &&
-               !has_parallel_hazard((Node *) parse->havingQual, false))
+               is_parallel_safe(root, (Node *) target->exprs) &&
+               is_parallel_safe(root, (Node *) parse->havingQual))
                grouped_rel->consider_parallel = true;
 
        /*
@@ -3881,8 +3896,8 @@ create_window_paths(PlannerInfo *root,
         * target list and active windows for non-parallel-safe constructs.
         */
        if (input_rel->consider_parallel &&
-               !has_parallel_hazard((Node *) output_target->exprs, false) &&
-               !has_parallel_hazard((Node *) activeWindows, false))
+               is_parallel_safe(root, (Node *) output_target->exprs) &&
+               is_parallel_safe(root, (Node *) activeWindows))
                window_rel->consider_parallel = true;
 
        /*
@@ -4272,7 +4287,7 @@ create_ordered_paths(PlannerInfo *root,
         * target list is parallel-safe.
         */
        if (input_rel->consider_parallel &&
-               !has_parallel_hazard((Node *) target->exprs, false))
+               is_parallel_safe(root, (Node *) target->exprs))
                ordered_rel->consider_parallel = true;
 
        /*
index a40ad406065623afc34d2e262d7b03ba0ddbe1a0..4496fde056d580ae27266149d395111652cd605e 100644 (file)
@@ -91,8 +91,9 @@ typedef struct
 
 typedef struct
 {
-       bool            allow_restricted;
-} has_parallel_hazard_arg;
+       char            max_hazard;             /* worst proparallel hazard found so far */
+       char            max_interesting;        /* worst proparallel hazard of interest */
+} max_parallel_hazard_context;
 
 static bool contain_agg_clause_walker(Node *node, void *context);
 static bool get_agg_clause_costs_walker(Node *node,
@@ -103,8 +104,8 @@ static bool contain_subplans_walker(Node *node, void *context);
 static bool contain_mutable_functions_walker(Node *node, void *context);
 static bool contain_volatile_functions_walker(Node *node, void *context);
 static bool contain_volatile_functions_not_nextval_walker(Node *node, void *context);
-static bool has_parallel_hazard_walker(Node *node,
-                                                  has_parallel_hazard_arg *context);
+static bool max_parallel_hazard_walker(Node *node,
+                                                  max_parallel_hazard_context *context);
 static bool contain_nonstrict_functions_walker(Node *node, void *context);
 static bool contain_context_dependent_node(Node *clause);
 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)
                                                                  context);
 }
 
+
 /*****************************************************************************
  *             Check queries for parallel unsafe and/or restricted constructs
  *****************************************************************************/
 
 /*
- * Check whether a node tree contains parallel hazards.  This is used both on
- * the entire query tree, to see whether the query can be parallelized at all
- * (with allow_restricted = true), and also to evaluate whether a particular
- * expression is safe to run within a parallel worker (with allow_restricted =
- * false).  We could separate these concerns into two different functions, but
- * there's enough overlap that it doesn't seem worthwhile.
+ * max_parallel_hazard
+ *             Find the worst parallel-hazard level in the given query
+ *
+ * Returns the worst function hazard property (the earliest in this list:
+ * PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, PROPARALLEL_SAFE) that can
+ * be found in the given parsetree.  We use this to find out whether the query
+ * can be parallelized at all.  The caller will also save the result in
+ * PlannerGlobal so as to short-circuit checks of portions of the querytree
+ * later, in the common case where everything is SAFE.
+ */
+char
+max_parallel_hazard(Query *parse)
+{
+       max_parallel_hazard_context context;
+
+       context.max_hazard = PROPARALLEL_SAFE;
+       context.max_interesting = PROPARALLEL_UNSAFE;
+       (void) max_parallel_hazard_walker((Node *) parse, &context);
+       return context.max_hazard;
+}
+
+/*
+ * is_parallel_safe
+ *             Detect whether the given expr contains only parallel-safe functions
+ *
+ * root->glob->maxParallelHazard must previously have been set to the
+ * result of max_parallel_hazard() on the whole query.
  */
 bool
-has_parallel_hazard(Node *node, bool allow_restricted)
+is_parallel_safe(PlannerInfo *root, Node *node)
 {
-       has_parallel_hazard_arg context;
+       max_parallel_hazard_context context;
 
-       context.allow_restricted = allow_restricted;
-       return has_parallel_hazard_walker(node, &context);
+       /* If max_parallel_hazard found nothing unsafe, we don't need to look */
+       if (root->glob->maxParallelHazard == PROPARALLEL_SAFE)
+               return true;
+       /* Else use max_parallel_hazard's search logic, but stop on RESTRICTED */
+       context.max_hazard = PROPARALLEL_SAFE;
+       context.max_interesting = PROPARALLEL_RESTRICTED;
+       return !max_parallel_hazard_walker(node, &context);
 }
 
+/* core logic for all parallel-hazard checks */
 static bool
-has_parallel_hazard_checker(Oid func_id, void *context)
+max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
 {
-       char            proparallel = func_parallel(func_id);
+       switch (proparallel)
+       {
+               case PROPARALLEL_SAFE:
+                       /* nothing to see here, move along */
+                       break;
+               case PROPARALLEL_RESTRICTED:
+                       /* increase max_hazard to RESTRICTED */
+                       Assert(context->max_hazard != PROPARALLEL_UNSAFE);
+                       context->max_hazard = proparallel;
+                       /* done if we are not expecting any unsafe functions */
+                       if (context->max_interesting == proparallel)
+                               return true;
+                       break;
+               case PROPARALLEL_UNSAFE:
+                       context->max_hazard = proparallel;
+                       /* we're always done at the first unsafe construct */
+                       return true;
+               default:
+                       elog(ERROR, "unrecognized proparallel value \"%c\"", proparallel);
+                       break;
+       }
+       return false;
+}
 
-       if (((has_parallel_hazard_arg *) context)->allow_restricted)
-               return (proparallel == PROPARALLEL_UNSAFE);
-       else
-               return (proparallel != PROPARALLEL_SAFE);
+/* check_functions_in_node callback */
+static bool
+max_parallel_hazard_checker(Oid func_id, void *context)
+{
+       return max_parallel_hazard_test(func_parallel(func_id),
+                                                                       (max_parallel_hazard_context *) context);
 }
 
 static bool
-has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
+max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 {
        if (node == NULL)
                return false;
 
        /* Check for hazardous functions in node itself */
-       if (check_functions_in_node(node, has_parallel_hazard_checker,
+       if (check_functions_in_node(node, max_parallel_hazard_checker,
                                                                context))
                return true;
 
@@ -1156,7 +1209,7 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
         */
        if (IsA(node, CoerceToDomain))
        {
-               if (!context->allow_restricted)
+               if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
                        return true;
        }
 
@@ -1167,7 +1220,7 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
        {
                RestrictInfo *rinfo = (RestrictInfo *) node;
 
-               return has_parallel_hazard_walker((Node *) rinfo->clause, context);
+               return max_parallel_hazard_walker((Node *) rinfo->clause, context);
        }
 
        /*
@@ -1176,13 +1229,13 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
         * not worry about examining their contents; if they are unsafe, we would
         * have found that out while examining the whole tree before reduction of
         * sublinks to subplans.  (Really we should not see SubLink during a
-        * not-allow_restricted scan, but if we do, return true.)
+        * max_interesting == restricted scan, but if we do, return true.)
         */
        else if (IsA(node, SubLink) ||
                         IsA(node, SubPlan) ||
                         IsA(node, AlternativeSubPlan))
        {
-               if (!context->allow_restricted)
+               if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
                        return true;
        }
 
@@ -1192,7 +1245,7 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
         */
        else if (IsA(node, Param))
        {
-               if (!context->allow_restricted)
+               if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
                        return true;
        }
 
@@ -1207,20 +1260,24 @@ has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
 
                /* SELECT FOR UPDATE/SHARE must be treated as unsafe */
                if (query->rowMarks != NULL)
+               {
+                       context->max_hazard = PROPARALLEL_UNSAFE;
                        return true;
+               }
 
                /* Recurse into subselects */
                return query_tree_walker(query,
-                                                                has_parallel_hazard_walker,
+                                                                max_parallel_hazard_walker,
                                                                 context, 0);
        }
 
        /* Recurse to check arguments */
        return expression_tree_walker(node,
-                                                                 has_parallel_hazard_walker,
+                                                                 max_parallel_hazard_walker,
                                                                  context);
 }
 
+
 /*****************************************************************************
  *             Check clauses for nonstrict functions
  *****************************************************************************/
index ce7ad545a95fcebc008f6a5254eaff3b1ac0d574..abb7507d8e4b8f641740d2651457bf8a0f157190 100644 (file)
@@ -2178,7 +2178,7 @@ create_projection_path(PlannerInfo *root,
        pathnode->path.parallel_aware = false;
        pathnode->path.parallel_safe = rel->consider_parallel &&
                subpath->parallel_safe &&
-               !has_parallel_hazard((Node *) target->exprs, false);
+               is_parallel_safe(root, (Node *) target->exprs);
        pathnode->path.parallel_workers = subpath->parallel_workers;
        /* Projection does not change the sort order */
        pathnode->path.pathkeys = subpath->pathkeys;
@@ -2285,7 +2285,7 @@ apply_projection_to_path(PlannerInfo *root,
         * target expressions, then we can't.
         */
        if (IsA(path, GatherPath) &&
-               !has_parallel_hazard((Node *) target->exprs, false))
+               is_parallel_safe(root, (Node *) target->exprs))
        {
                GatherPath *gpath = (GatherPath *) path;
 
@@ -2306,7 +2306,7 @@ apply_projection_to_path(PlannerInfo *root,
                                                                   target);
        }
        else if (path->parallel_safe &&
-                        has_parallel_hazard((Node *) target->exprs, false))
+                        !is_parallel_safe(root, (Node *) target->exprs))
        {
                /*
                 * We're inserting a parallel-restricted target list into a path
index 806600ed107d1a0c5db1081190e78921556cce1d..deef5605b7dcc34d9b2cfd8608620c9fadeb24ac 100644 (file)
@@ -513,8 +513,8 @@ build_join_rel(PlannerInfo *root,
         * here.
         */
        if (inner_rel->consider_parallel && outer_rel->consider_parallel &&
-               !has_parallel_hazard((Node *) restrictlist, false) &&
-               !has_parallel_hazard((Node *) joinrel->reltarget->exprs, false))
+               is_parallel_safe(root, (Node *) restrictlist) &&
+               is_parallel_safe(root, (Node *) joinrel->reltarget->exprs))
                joinrel->consider_parallel = true;
 
        /*
index 2be8908445b03cdcecc8786a5128aac8015ff890..fcfb0d4d0ffaeb882b5fbfe6848dea86e4766307 100644 (file)
@@ -126,6 +126,8 @@ typedef struct PlannerGlobal
        bool            parallelModeOK; /* parallel mode potentially OK? */
 
        bool            parallelModeNeeded;             /* parallel mode actually required? */
+
+       char            maxParallelHazard;              /* worst PROPARALLEL hazard level */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
index be7c639f7b93a6b21de532748f98b886960474d8..9abef37cb6bae92cacab566631c5fa2bba295cf1 100644 (file)
@@ -61,7 +61,8 @@ extern bool contain_subplans(Node *clause);
 extern bool contain_mutable_functions(Node *clause);
 extern bool contain_volatile_functions(Node *clause);
 extern bool contain_volatile_functions_not_nextval(Node *clause);
-extern bool has_parallel_hazard(Node *node, bool allow_restricted);
+extern char max_parallel_hazard(Query *parse);
+extern bool is_parallel_safe(PlannerInfo *root, Node *node);
 extern bool contain_nonstrict_functions(Node *clause);
 extern bool contain_leaked_vars(Node *clause);