Introduce a new GUC force_parallel_mode for testing purposes.
authorRobert Haas <[email protected]>
Sun, 7 Feb 2016 16:39:22 +0000 (11:39 -0500)
committerRobert Haas <[email protected]>
Sun, 7 Feb 2016 16:41:33 +0000 (11:41 -0500)
When force_parallel_mode = true, we enable the parallel mode restrictions
for all queries for which this is believed to be safe.  For the subset of
those queries believed to be safe to run entirely within a worker, we spin
up a worker and run the query there instead of running it in the
original process.  When force_parallel_mode = regress, make additional
changes to allow the regression tests to run cleanly even though parallel
workers have been injected under the hood.

Taken together, this facilitates both better user testing and better
regression testing of the parallelism code.

Robert Haas, with help from Amit Kapila and Rushabh Lathia.

13 files changed:
doc/src/sgml/config.sgml
src/backend/access/transam/parallel.c
src/backend/commands/explain.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/planner.c
src/backend/utils/misc/guc.c
src/backend/utils/misc/postgresql.conf.sample
src/include/nodes/plannodes.h
src/include/nodes/relation.h
src/include/optimizer/planmain.h

index 392eb700b0cc181dfc18776d5a13ad640e6a16e1..de84b7773002eec2db469af69092b856c749d88f 100644 (file)
@@ -3802,6 +3802,51 @@ SELECT * FROM parent WHERE key = 2400;
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-force-parallel-mode" xreflabel="force_parallel_mode">
+      <term><varname>force_parallel_mode</varname> (<type>enum</type>)
+      <indexterm>
+       <primary><varname>force_parallel_mode</> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Allows the use of parallel queries for testing purposes even in cases
+        where no performance benefit is expected.
+        The allowed values of <varname>force_parallel_mode</> are
+        <literal>off</> (use parallel mode only when it is expected to improve
+        performance), <literal>on</> (force parallel query for all queries
+        for which it is thought to be safe), and <literal>regress</> (like
+        on, but with additional behavior changes to facilitate automated
+        regression testing).
+       </para>
+
+       <para>
+        More specifically, setting this value to <literal>on</> will add
+        a <literal>Gather</> node to the top of any query plan for which this
+        appears to be safe, so that the query runs inside of a parallel worker.
+        Even when a parallel worker is not available or cannot be used,
+        operations such as starting a subtransaction that would be prohibited
+        in a parallel query context will be prohibited unless the planner
+        believes that this will cause the query to fail.  If failures or
+        unexpected results occur when this option is set, some functions used
+        by the query may need to be marked <literal>PARALLEL UNSAFE</literal>
+        (or, possibly, <literal>PARALLEL RESTRICTED</literal>).
+       </para>
+
+       <para>
+        Setting this value to <literal>regress</> has all of the same effects
+        as setting it to <literal>on</> plus some additional effect that are
+        intended to facilitate automated regression testing.  Normally,
+        messages from a parallel worker are prefixed with a context line,
+        but a setting of <literal>regress</> suppresses this to guarantee
+        reproducible results.  Also, the <literal>Gather</> nodes added to
+        plans by this setting are hidden from the <literal>EXPLAIN</> output
+        so that the output matches what would be obtained if this setting
+        were turned <literal>off</>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
    </sect1>
index bf2e691f577680779f6f433744cbc5b44a972a59..4f91cd0265db041c671ab39ace93dd70b7a170ea 100644 (file)
@@ -22,6 +22,7 @@
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
+#include "optimizer/planmain.h"
 #include "storage/ipc.h"
 #include "storage/sinval.h"
 #include "storage/spin.h"
@@ -1079,7 +1080,8 @@ ParallelExtensionTrampoline(dsm_segment *seg, shm_toc *toc)
 static void
 ParallelErrorContext(void *arg)
 {
-   errcontext("parallel worker, PID %d", *(int32 *) arg);
+   if (force_parallel_mode != FORCE_PARALLEL_REGRESS)
+       errcontext("parallel worker, PID %d", *(int32 *) arg);
 }
 
 /*
index 25d8ca075d4e938404533b48326b0bcf546a606b..ee13136b7fd6c11c3529d86c590ac98c0cfef835 100644 (file)
@@ -23,6 +23,7 @@
 #include "foreign/fdwapi.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
 #include "tcop/tcopprot.h"
@@ -572,6 +573,7 @@ void
 ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
 {
    Bitmapset  *rels_used = NULL;
+   PlanState *ps;
 
    Assert(queryDesc->plannedstmt != NULL);
    es->pstmt = queryDesc->plannedstmt;
@@ -580,7 +582,17 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
    es->rtable_names = select_rtable_names_for_explain(es->rtable, rels_used);
    es->deparse_cxt = deparse_context_for_plan_rtable(es->rtable,
                                                      es->rtable_names);
-   ExplainNode(queryDesc->planstate, NIL, NULL, NULL, es);
+
+   /*
+    * Sometimes we mark a Gather node as "invisible", which means that it's
+    * not displayed in EXPLAIN output.  The purpose of this is to allow
+    * running regression tests with force_parallel_mode=regress to get the
+    * same results as running the same tests with force_parallel_mode=off.
+    */
+   ps = queryDesc->planstate;
+   if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
+       ps = outerPlanState(ps);
+   ExplainNode(ps, NIL, NULL, NULL, es);
 }
 
 /*
index a8b79fa8c31e0d6641c6b57cccfc577dc74060f1..e54d1744b0a2e86db9b3cd557281fc6e1b4bdac7 100644 (file)
@@ -334,6 +334,7 @@ _copyGather(const Gather *from)
     */
    COPY_SCALAR_FIELD(num_workers);
    COPY_SCALAR_FIELD(single_copy);
+   COPY_SCALAR_FIELD(invisible);
 
    return newnode;
 }
index d59b95465431cc41cc8492fdbdbacd05a041bf6e..3e1c3e6be572a996c78855aceba48a5a090cd75b 100644 (file)
@@ -443,6 +443,7 @@ _outGather(StringInfo str, const Gather *node)
 
    WRITE_INT_FIELD(num_workers);
    WRITE_BOOL_FIELD(single_copy);
+   WRITE_BOOL_FIELD(invisible);
 }
 
 static void
@@ -1824,6 +1825,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
    WRITE_BOOL_FIELD(hasRowSecurity);
    WRITE_BOOL_FIELD(parallelModeOK);
    WRITE_BOOL_FIELD(parallelModeNeeded);
+   WRITE_BOOL_FIELD(wholePlanParallelSafe);
    WRITE_BOOL_FIELD(hasForeignJoin);
 }
 
index 6c461513d6434e0b2832935a7673072f50d7186e..e4d41ee95b2e707bd459e4f501d216675ef3dcd2 100644 (file)
@@ -2053,6 +2053,7 @@ _readGather(void)
 
    READ_INT_FIELD(num_workers);
    READ_BOOL_FIELD(single_copy);
+   READ_BOOL_FIELD(invisible);
 
    READ_DONE();
 }
index 54ff7f623d45137d396390b4082a29ad3a72bb3b..6e0db0803832ab22bc870bb7e2592e33ab0c6769 100644 (file)
@@ -212,6 +212,10 @@ create_plan(PlannerInfo *root, Path *best_path)
    /* Recursively process the path tree */
    plan = create_plan_recurse(root, best_path);
 
+   /* Update parallel safety information if needed. */
+   if (!best_path->parallel_safe)
+       root->glob->wholePlanParallelSafe = false;
+
    /* Check we successfully assigned all NestLoopParams to plan nodes */
    if (root->curOuterParams != NIL)
        elog(ERROR, "failed to assign all NestLoopParams to plan nodes");
@@ -4829,6 +4833,7 @@ make_gather(List *qptlist,
    plan->righttree = NULL;
    node->num_workers = nworkers;
    node->single_copy = single_copy;
+   node->invisible = false;
 
    return node;
 }
index a09b4b5b4798b1921b05da248946025c523a0e1b..a3cc27464c10c39b02df64a13ca2d54e1359d565 100644 (file)
 #include "storage/dsm_impl.h"
 #include "utils/rel.h"
 #include "utils/selfuncs.h"
+#include "utils/syscache.h"
 
 
-/* GUC parameter */
+/* GUC parameters */
 double     cursor_tuple_fraction = DEFAULT_CURSOR_TUPLE_FRACTION;
+int            force_parallel_mode = FORCE_PARALLEL_OFF;
 
 /* Hook for plugins to get control in planner() */
 planner_hook_type planner_hook = NULL;
@@ -230,25 +232,31 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
        !has_parallel_hazard((Node *) parse, true);
 
    /*
-    * glob->parallelModeOK should tell us whether it's necessary to impose
-    * the parallel mode restrictions, but we don't actually want to impose
-    * them unless we choose a parallel plan, so that people who mislabel
-    * their functions but don't use parallelism anyway aren't harmed.
-    * However, it's useful for testing purposes to be able to force the
-    * restrictions to be imposed whenever a parallel plan is actually chosen
-    * or not.
+    * glob->parallelModeNeeded should tell us whether it's necessary to
+    * impose the parallel mode restrictions, but we don't actually want to
+    * impose them unless we choose a parallel plan, so that people who
+    * mislabel their functions but don't use parallelism anyway aren't
+    * harmed. But when force_parallel_mode is set, we enable the restrictions
+    * whenever possible for testing purposes.
     *
-    * (It's been suggested that we should always impose these restrictions
-    * whenever glob->parallelModeOK is true, so that it's easier to notice
-    * incorrectly-labeled functions sooner.  That might be the right thing to
-    * do, but for now I've taken this approach.  We could also control this
-    * with a GUC.)
+    * glob->wholePlanParallelSafe should tell us whether it's OK to stick a
+    * Gather node on top of the entire plan.  However, it only needs to be
+    * accurate when force_parallel_mode is 'on' or 'regress', so we don't
+    * bother doing the work otherwise.  The value we set here is just a
+    * preliminary guess; it may get changed from true to false later, but
+    * not visca versa.
     */
-#ifdef FORCE_PARALLEL_MODE
-   glob->parallelModeNeeded = glob->parallelModeOK;
-#else
-   glob->parallelModeNeeded = false;
-#endif
+   if (force_parallel_mode == FORCE_PARALLEL_OFF || !glob->parallelModeOK)
+   {
+       glob->parallelModeNeeded = false;
+       glob->wholePlanParallelSafe = false;    /* either false or don't care */
+   }
+   else
+   {
+       glob->parallelModeNeeded = true;
+       glob->wholePlanParallelSafe =
+           !has_parallel_hazard((Node *) parse, false);
+   }
 
    /* Determine what fraction of the plan is likely to be scanned */
    if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -292,6 +300,35 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
            top_plan = materialize_finished_plan(top_plan);
    }
 
+   /*
+    * At present, we don't copy subplans to workers.  The presence of a
+    * subplan in one part of the plan doesn't preclude the use of parallelism
+    * in some other part of the plan, but it does preclude the possibility of
+    * regarding the entire plan parallel-safe.
+    */
+   if (glob->subplans != NULL)
+       glob->wholePlanParallelSafe = false;
+
+   /*
+    * Optionally add a Gather node for testing purposes, provided this is
+    * actually a safe thing to do.
+    */
+   if (glob->wholePlanParallelSafe &&
+       force_parallel_mode != FORCE_PARALLEL_OFF)
+   {
+       Gather     *gather = makeNode(Gather);
+
+       gather->plan.targetlist = top_plan->targetlist;
+       gather->plan.qual = NIL;
+       gather->plan.lefttree = top_plan;
+       gather->plan.righttree = NULL;
+       gather->num_workers = 1;
+       gather->single_copy = true;
+       gather->invisible = (force_parallel_mode == FORCE_PARALLEL_REGRESS);
+       root->glob->parallelModeNeeded = true;
+       top_plan = &gather->plan;
+   }
+
    /*
     * If any Params were generated, run through the plan tree and compute
     * each plan node's extParam/allParam sets.  Ideally we'd merge this into
index 66c479141f3dec953e647cd3337361f53f74c5f3..31a69cac72342acfc5de142851f7b2ef1904420d 100644 (file)
@@ -379,6 +379,19 @@ static const struct config_enum_entry huge_pages_options[] = {
    {NULL, 0, false}
 };
 
+static const struct config_enum_entry force_parallel_mode_options[] = {
+   {"off", FORCE_PARALLEL_OFF, false},
+   {"on", FORCE_PARALLEL_ON, false},
+   {"regress", FORCE_PARALLEL_REGRESS, false},
+   {"true", FORCE_PARALLEL_ON, true},
+   {"false", FORCE_PARALLEL_OFF, true},
+   {"yes", FORCE_PARALLEL_ON, true},
+   {"no", FORCE_PARALLEL_OFF, true},
+   {"1", FORCE_PARALLEL_ON, true},
+   {"0", FORCE_PARALLEL_OFF, true},
+   {NULL, 0, false}
+};
+
 /*
  * Options for enum values stored in other modules
  */
@@ -863,6 +876,7 @@ static struct config_bool ConfigureNamesBool[] =
        true,
        NULL, NULL, NULL
    },
+
    {
        {"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
            gettext_noop("Enables genetic query optimization."),
@@ -3672,6 +3686,16 @@ static struct config_enum ConfigureNamesEnum[] =
        NULL, NULL, NULL
    },
 
+   {
+       {"force_parallel_mode", PGC_USERSET, QUERY_TUNING_OTHER,
+           gettext_noop("Forces use of parallel query facilities."),
+           gettext_noop("If possible, run query using a parallel worker and with parallel restrictions.")
+       },
+       &force_parallel_mode,
+       FORCE_PARALLEL_OFF, force_parallel_mode_options,
+       NULL, NULL, NULL
+   },
+
    /* End-of-list marker */
    {
        {NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL
index 029114fc22d9fee77a6bd76626e3e264e210592b..09b2003dbe2f3b9761fee7e8ee9b0bfd6cd350e2 100644 (file)
 #from_collapse_limit = 8
 #join_collapse_limit = 8       # 1 disables collapsing of explicit
                    # JOIN clauses
+#force_parallel_mode = off
 
 
 #------------------------------------------------------------------------------
index 55d6bbe8f0e680ea555dd332ada5b4b2aa27dd50..ae224cfa314d765a8af91f7c62deb90d6f7ef994 100644 (file)
@@ -775,6 +775,7 @@ typedef struct Gather
    Plan        plan;
    int         num_workers;
    bool        single_copy;
+   bool        invisible;      /* suppress EXPLAIN display (for testing)? */
 } Gather;
 
 /* ----------------
index 595438cb24dfb99ea264df10fa4136441c485933..96198aeec18a4415d1b220f679d4aa53bacc3a5c 100644 (file)
@@ -108,6 +108,9 @@ typedef struct PlannerGlobal
    bool        parallelModeOK; /* parallel mode potentially OK? */
 
    bool        parallelModeNeeded;     /* parallel mode actually required? */
+
+   bool        wholePlanParallelSafe;  /* is the entire plan parallel safe? */
+
    bool        hasForeignJoin; /* does have a pushed down foreign join */
 } PlannerGlobal;
 
index 7ae73676e8e87dde6232ef386dc46eb528e304b8..eaa642bc57e58e67483e29ea4f6adaf6c3a992a4 100644 (file)
 #include "nodes/plannodes.h"
 #include "nodes/relation.h"
 
+/* possible values for force_parallel_mode */
+typedef enum
+{
+   FORCE_PARALLEL_OFF,
+   FORCE_PARALLEL_ON,
+   FORCE_PARALLEL_REGRESS
+} ForceParallelMode;
+
 /* GUC parameters */
 #define DEFAULT_CURSOR_TUPLE_FRACTION 0.1
 extern double cursor_tuple_fraction;
+extern int force_parallel_mode;
 
 /* query_planner callback to compute query_pathkeys */
 typedef void (*query_pathkeys_callback) (PlannerInfo *root, void *extra);