Improving planner's checks for parallel-unsafety - Mailing list pgsql-hackers

From Tom Lane
Subject Improving planner's checks for parallel-unsafety
Date
Msg-id 3740.1471538387@sss.pgh.pa.us
Whole thread Raw
Responses Re: Improving planner's checks for parallel-unsafety  (Robert Haas <robertmhaas@gmail.com>)
List pgsql-hackers
Attached is a patch I'd fooled around with back in July but not submitted.
The idea is that, if our initial scan of the query tree found only
parallel-safe functions, there is no need to rescan subsets of the tree
looking for parallel-restricted functions.  We can mechanize that by
saving the "maximum unsafety" level in PlannerGlobal and looking aside
at that value before conducting a check of a subset of the tree.

This is not a huge win, but it's measurable.  I see about 3% overall TPS
improvement in pgbench on repeated execution of this test query:

select
 abs(unique1) + abs(unique1),
 abs(unique2) + abs(unique2),
 abs(two) + abs(two),
 abs(four) + abs(four),
 abs(ten) + abs(ten),
 abs(twenty) + abs(twenty),
 abs(hundred) + abs(hundred),
 abs(thousand) + abs(thousand),
 abs(twothousand) + abs(twothousand),
 abs(fivethous) + abs(fivethous),
 abs(tenthous) + abs(tenthous),
 abs(odd) + abs(odd),
 abs(even) + abs(even)
from tenk1 limit 1;

This test case is admittedly a bit contrived, in that the number of
function calls that have to be checked is high relative to both the
planning cost and execution cost of the query.  Still, the fact that
the difference is above the noise floor even in an end-to-end test
says that the current method of checking functions twice is pretty
inefficient.

I'll put this in the commitfest queue.

            regards, tom lane

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1fab807..50019f4 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outPlannerGlobal(StringInfo str, const
*** 2029,2034 ****
--- 2029,2035 ----
      WRITE_BOOL_FIELD(dependsOnRole);
      WRITE_BOOL_FIELD(parallelModeOK);
      WRITE_BOOL_FIELD(parallelModeNeeded);
+     WRITE_CHAR_FIELD(maxParallelHazard);
  }

  static void
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 88d833a..365161c 100644
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** static void set_plain_rel_size(PlannerIn
*** 78,84 ****
  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,
--- 78,84 ----
  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(PlannerInfo *root, RangeTblEntry *rte);
  static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
                         RangeTblEntry *rte);
  static void set_tablesample_rel_size(PlannerInfo *root, RelOptInfo *rel,
*************** set_rel_consider_parallel(PlannerInfo *r
*** 542,549 ****

                  if (proparallel != PROPARALLEL_SAFE)
                      return;
!                 if (has_parallel_hazard((Node *) rte->tablesample->args,
!                                         false))
                      return;
              }

--- 542,548 ----

                  if (proparallel != PROPARALLEL_SAFE)
                      return;
!                 if (!is_parallel_safe(root, (Node *) rte->tablesample->args))
                      return;
              }

*************** set_rel_consider_parallel(PlannerInfo *r
*** 596,602 ****

          case RTE_FUNCTION:
              /* Check for parallel-restricted functions. */
!             if (!function_rte_parallel_ok(rte))
                  return;
              break;

--- 595,601 ----

          case RTE_FUNCTION:
              /* Check for parallel-restricted functions. */
!             if (!function_rte_parallel_ok(root, rte))
                  return;
              break;

*************** set_rel_consider_parallel(PlannerInfo *r
*** 629,642 ****
       * outer join clauses work correctly.  It would likely break equivalence
       * classes, too.
       */
!     if (has_parallel_hazard((Node *) rel->baserestrictinfo, false))
          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))
          return;

      /* We have a winner. */
--- 628,641 ----
       * outer join clauses work correctly.  It would likely break equivalence
       * classes, too.
       */
!     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 (!is_parallel_safe(root, (Node *) rel->reltarget->exprs))
          return;

      /* We have a winner. */
*************** set_rel_consider_parallel(PlannerInfo *r
*** 647,653 ****
   * Check whether a function RTE is scanning something parallel-restricted.
   */
  static bool
! function_rte_parallel_ok(RangeTblEntry *rte)
  {
      ListCell   *lc;

--- 646,652 ----
   * Check whether a function RTE is scanning something parallel-restricted.
   */
  static bool
! function_rte_parallel_ok(PlannerInfo *root, RangeTblEntry *rte)
  {
      ListCell   *lc;

*************** function_rte_parallel_ok(RangeTblEntry *
*** 656,662 ****
          RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);

          Assert(IsA(rtfunc, RangeTblFunction));
!         if (has_parallel_hazard(rtfunc->funcexpr, false))
              return false;
      }

--- 655,661 ----
          RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);

          Assert(IsA(rtfunc, RangeTblFunction));
!         if (!is_parallel_safe(root, rtfunc->funcexpr))
              return false;
      }

diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 27234ff..e7ae7ae 100644
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** query_planner(PlannerInfo *root, List *t
*** 71,84 ****

          /*
           * 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.
           */
          if (root->glob->parallelModeOK)
              final_rel->consider_parallel =
!                 !has_parallel_hazard(parse->jointree->quals, false);

          /* The only path for it is a trivial Result path */
          add_path(final_rel, (Path *)
--- 71,83 ----

          /*
           * If query allows parallelism in general, check whether the 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.)
           */
          if (root->glob->parallelModeOK)
              final_rel->consider_parallel =
!                 is_parallel_safe(root, parse->jointree->quals);

          /* The only path for it is a trivial Result path */
          add_path(final_rel, (Path *)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b265628..174210b 100644
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 23,28 ****
--- 23,29 ----
  #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"
*************** standard_planner(Query *parse, int curso
*** 241,252 ****
       * 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);

      /*
       * glob->parallelModeNeeded should tell us whether it's necessary to
--- 242,267 ----
       * time and execution time, so don't generate a parallel plan if we're in
       * serializable mode.
       */
!     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
*************** grouping_planner(PlannerInfo *root, bool
*** 1802,1808 ****
           * computed by partial paths.
           */
          if (current_rel->partial_pathlist &&
!             !has_parallel_hazard((Node *) scanjoin_target->exprs, false))
          {
              /* Apply the scan/join target to each partial path */
              foreach(lc, current_rel->partial_pathlist)
--- 1817,1823 ----
           * computed by partial paths.
           */
          if (current_rel->partial_pathlist &&
!             is_parallel_safe(root, (Node *) scanjoin_target->exprs))
          {
              /* Apply the scan/join target to each partial path */
              foreach(lc, current_rel->partial_pathlist)
*************** grouping_planner(PlannerInfo *root, bool
*** 1948,1955 ****
       * query.
       */
      if (current_rel->consider_parallel &&
!         !has_parallel_hazard(parse->limitOffset, false) &&
!         !has_parallel_hazard(parse->limitCount, false))
          final_rel->consider_parallel = true;

      /*
--- 1963,1970 ----
       * query.
       */
      if (current_rel->consider_parallel &&
!         is_parallel_safe(root, parse->limitOffset) &&
!         is_parallel_safe(root, parse->limitCount))
          final_rel->consider_parallel = true;

      /*
*************** create_grouping_paths(PlannerInfo *root,
*** 3326,3333 ****
       * 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))
          grouped_rel->consider_parallel = true;

      /*
--- 3341,3348 ----
       * target list and HAVING quals are parallel-safe.
       */
      if (input_rel->consider_parallel &&
!         is_parallel_safe(root, (Node *) target->exprs) &&
!         is_parallel_safe(root, (Node *) parse->havingQual))
          grouped_rel->consider_parallel = true;

      /*
*************** create_window_paths(PlannerInfo *root,
*** 3881,3888 ****
       * 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))
          window_rel->consider_parallel = true;

      /*
--- 3896,3903 ----
       * target list and active windows for non-parallel-safe constructs.
       */
      if (input_rel->consider_parallel &&
!         is_parallel_safe(root, (Node *) output_target->exprs) &&
!         is_parallel_safe(root, (Node *) activeWindows))
          window_rel->consider_parallel = true;

      /*
*************** create_ordered_paths(PlannerInfo *root,
*** 4272,4278 ****
       * target list is parallel-safe.
       */
      if (input_rel->consider_parallel &&
!         !has_parallel_hazard((Node *) target->exprs, false))
          ordered_rel->consider_parallel = true;

      /*
--- 4287,4293 ----
       * target list is parallel-safe.
       */
      if (input_rel->consider_parallel &&
!         is_parallel_safe(root, (Node *) target->exprs))
          ordered_rel->consider_parallel = true;

      /*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a40ad40..85220ec 100644
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
*************** typedef struct
*** 91,98 ****

  typedef struct
  {
!     bool        allow_restricted;
! } has_parallel_hazard_arg;

  static bool contain_agg_clause_walker(Node *node, void *context);
  static bool get_agg_clause_costs_walker(Node *node,
--- 91,99 ----

  typedef struct
  {
!     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,
*************** static bool contain_subplans_walker(Node
*** 103,110 ****
  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 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);
--- 104,111 ----
  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 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);
*************** contain_volatile_functions_not_nextval_w
*** 1100,1145 ****
                                    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.
   */
  bool
! has_parallel_hazard(Node *node, bool allow_restricted)
  {
!     has_parallel_hazard_arg context;

!     context.allow_restricted = allow_restricted;
!     return has_parallel_hazard_walker(node, &context);
  }

  static bool
! has_parallel_hazard_checker(Oid func_id, void *context)
  {
!     char        proparallel = func_parallel(func_id);

!     if (((has_parallel_hazard_arg *) context)->allow_restricted)
!         return (proparallel == PROPARALLEL_UNSAFE);
!     else
!         return (proparallel != PROPARALLEL_SAFE);
  }

  static bool
! has_parallel_hazard_walker(Node *node, has_parallel_hazard_arg *context)
  {
      if (node == NULL)
          return false;

      /* Check for hazardous functions in node itself */
!     if (check_functions_in_node(node, has_parallel_hazard_checker,
                                  context))
          return true;

--- 1101,1197 ----
                                    context);
  }

+
  /*****************************************************************************
   *        Check queries for parallel unsafe and/or restricted constructs
   *****************************************************************************/

  /*
!  * max_parallel_hazard
!  *        Find the worst parallel-hazard level in the given query
!  *
!  * Returns the first of PROPARALLEL_UNSAFE, PROPARALLEL_RESTRICTED, or
!  * PROPARALLEL_SAFE that can be found in the given parsetree.  We use this
!  * to find out whether the query can be parallelized at all.  We 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
! is_parallel_safe(PlannerInfo *root, Node *node)
  {
!     max_parallel_hazard_context 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
! max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context)
  {
!     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;
! }

! /* 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
! 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, max_parallel_hazard_checker,
                                  context))
          return true;

*************** has_parallel_hazard_walker(Node *node, h
*** 1156,1162 ****
       */
      if (IsA(node, CoerceToDomain))
      {
!         if (!context->allow_restricted)
              return true;
      }

--- 1208,1214 ----
       */
      if (IsA(node, CoerceToDomain))
      {
!         if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
              return true;
      }

*************** has_parallel_hazard_walker(Node *node, h
*** 1167,1173 ****
      {
          RestrictInfo *rinfo = (RestrictInfo *) node;

!         return has_parallel_hazard_walker((Node *) rinfo->clause, context);
      }

      /*
--- 1219,1225 ----
      {
          RestrictInfo *rinfo = (RestrictInfo *) node;

!         return max_parallel_hazard_walker((Node *) rinfo->clause, context);
      }

      /*
*************** has_parallel_hazard_walker(Node *node, h
*** 1176,1188 ****
       * 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.)
       */
      else if (IsA(node, SubLink) ||
               IsA(node, SubPlan) ||
               IsA(node, AlternativeSubPlan))
      {
!         if (!context->allow_restricted)
              return true;
      }

--- 1228,1240 ----
       * 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
!      * max_interesting == restricted scan, but if we do, return true.)
       */
      else if (IsA(node, SubLink) ||
               IsA(node, SubPlan) ||
               IsA(node, AlternativeSubPlan))
      {
!         if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
              return true;
      }

*************** has_parallel_hazard_walker(Node *node, h
*** 1192,1198 ****
       */
      else if (IsA(node, Param))
      {
!         if (!context->allow_restricted)
              return true;
      }

--- 1244,1250 ----
       */
      else if (IsA(node, Param))
      {
!         if (max_parallel_hazard_test(PROPARALLEL_RESTRICTED, context))
              return true;
      }

*************** has_parallel_hazard_walker(Node *node, h
*** 1207,1226 ****

          /* SELECT FOR UPDATE/SHARE must be treated as unsafe */
          if (query->rowMarks != NULL)
              return true;

          /* Recurse into subselects */
          return query_tree_walker(query,
!                                  has_parallel_hazard_walker,
                                   context, 0);
      }

      /* Recurse to check arguments */
      return expression_tree_walker(node,
!                                   has_parallel_hazard_walker,
                                    context);
  }

  /*****************************************************************************
   *        Check clauses for nonstrict functions
   *****************************************************************************/
--- 1259,1282 ----

          /* 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,
!                                  max_parallel_hazard_walker,
                                   context, 0);
      }

      /* Recurse to check arguments */
      return expression_tree_walker(node,
!                                   max_parallel_hazard_walker,
                                    context);
  }

+
  /*****************************************************************************
   *        Check clauses for nonstrict functions
   *****************************************************************************/
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index ce7ad54..abb7507 100644
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** create_projection_path(PlannerInfo *root
*** 2178,2184 ****
      pathnode->path.parallel_aware = false;
      pathnode->path.parallel_safe = rel->consider_parallel &&
          subpath->parallel_safe &&
!         !has_parallel_hazard((Node *) target->exprs, false);
      pathnode->path.parallel_workers = subpath->parallel_workers;
      /* Projection does not change the sort order */
      pathnode->path.pathkeys = subpath->pathkeys;
--- 2178,2184 ----
      pathnode->path.parallel_aware = false;
      pathnode->path.parallel_safe = rel->consider_parallel &&
          subpath->parallel_safe &&
!         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;
*************** apply_projection_to_path(PlannerInfo *ro
*** 2285,2291 ****
       * target expressions, then we can't.
       */
      if (IsA(path, GatherPath) &&
!         !has_parallel_hazard((Node *) target->exprs, false))
      {
          GatherPath *gpath = (GatherPath *) path;

--- 2285,2291 ----
       * target expressions, then we can't.
       */
      if (IsA(path, GatherPath) &&
!         is_parallel_safe(root, (Node *) target->exprs))
      {
          GatherPath *gpath = (GatherPath *) path;

*************** apply_projection_to_path(PlannerInfo *ro
*** 2306,2312 ****
                                     target);
      }
      else if (path->parallel_safe &&
!              has_parallel_hazard((Node *) target->exprs, false))
      {
          /*
           * We're inserting a parallel-restricted target list into a path
--- 2306,2312 ----
                                     target);
      }
      else if (path->parallel_safe &&
!              !is_parallel_safe(root, (Node *) target->exprs))
      {
          /*
           * We're inserting a parallel-restricted target list into a path
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 806600e..deef560 100644
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
*************** build_join_rel(PlannerInfo *root,
*** 513,520 ****
       * here.
       */
      if (inner_rel->consider_parallel && outer_rel->consider_parallel &&
!         !has_parallel_hazard((Node *) restrictlist, false) &&
!         !has_parallel_hazard((Node *) joinrel->reltarget->exprs, false))
          joinrel->consider_parallel = true;

      /*
--- 513,520 ----
       * here.
       */
      if (inner_rel->consider_parallel && outer_rel->consider_parallel &&
!         is_parallel_safe(root, (Node *) restrictlist) &&
!         is_parallel_safe(root, (Node *) joinrel->reltarget->exprs))
          joinrel->consider_parallel = true;

      /*
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 2be8908..fcfb0d4 100644
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerGlobal
*** 126,131 ****
--- 126,133 ----
      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 */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index be7c639..9abef37 100644
*** a/src/include/optimizer/clauses.h
--- b/src/include/optimizer/clauses.h
*************** extern bool contain_subplans(Node *claus
*** 61,67 ****
  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 bool contain_nonstrict_functions(Node *clause);
  extern bool contain_leaked_vars(Node *clause);

--- 61,68 ----
  extern bool contain_mutable_functions(Node *clause);
  extern bool contain_volatile_functions(Node *clause);
  extern bool contain_volatile_functions_not_nextval(Node *clause);
! 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);


pgsql-hackers by date:

Previous
From: Andres Freund
Date:
Subject: Re: Pluggable storage
Next
From: Heikki Linnakangas
Date:
Subject: Re: Password identifiers, protocol aging and SCRAM protocol