Introduce a new GUC force_parallel_mode for testing purposes.
authorRobert Haas <rhaas@postgresql.org>
Wed, 30 Sep 2015 22:35:40 +0000 (18:35 -0400)
committerRobert Haas <rhaas@postgresql.org>
Mon, 1 Feb 2016 21:07:32 +0000 (16:07 -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.

XXX: This patch still needs some work yet as far as explain.c is
concerned.  Instead of what happens here at present, we should have a
new Boolean inside the Gather node, maybe "bool invisible".  explain.c
should look through invisible Gather nodes but not others;
force_parallel_mode should create an invisible Gather node rather than
a normal one.  No effect at execution time.

XXX: I think force_parallel_mode should have three modes: on, off,
regress.  When off, we act normally.  When on, we push regular, visible
Gather nodes on top of plans that can support them, and display error
context as usual.  The third mode, regress, turns the Gather nodes
invisible and suppresses the additional error context.

doc/src/sgml/config.sgml
src/backend/access/transam/parallel.c
src/backend/commands/explain.c
src/backend/nodes/outfuncs.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/relation.h
src/include/optimizer/planmain.h

index 392eb700b0cc181dfc18776d5a13ad640e6a16e1..81f9cbd4a72acc23822349d0ba08b6e0bf620f37 100644 (file)
@@ -3802,6 +3802,40 @@ 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>boolean</type>)
+      <indexterm>
+       <primary><varname>force_parallel_mode</> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        This setting is intended for testing purposes only.  It has
+        several different but related effects.  First, it forces parallel
+        mode restrictions to be enforced for all queries except those which
+        the query planner believes would fail to execute properly with
+        those restrictions in place.  If an error occurs when this option is
+        used, some functions used by the query may need to be marked
+        <literal>PARALLEL UNSAFE</literal>.
+       </para>
+
+       <para>
+        Second, if the finished plan appears to be safe to run within a
+        parallel worker process, then the query planner will do so.  This
+        provides a further test of the parallel query facilities and may
+        reveal additional problems with function markings.
+       </para>
+
+       <para>
+        Third, if a parallel worker prints an error or other message while
+        this option is enabled, the error context indicating that the message
+        originated from a parallel worker will be suppressed.  This makes
+        it possible to run the same set of regression tests with and without
+        this option enabled and expect to get the same results.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
    </sect1>
index bf2e691f577680779f6f433744cbc5b44a972a59..391575041959df24960830ac998099186c6715d9 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)
+               errcontext("parallel worker, PID %d", *(int32 *) arg);
 }
 
 /*
index 25d8ca075d4e938404533b48326b0bcf546a606b..3329d21ec09be9e5eabc30a0e4956339827002ef 100644 (file)
@@ -572,6 +572,7 @@ void
 ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
 {
        Bitmapset  *rels_used = NULL;
+       PlanState *ps;
 
        Assert(queryDesc->plannedstmt != NULL);
        es->pstmt = queryDesc->plannedstmt;
@@ -580,7 +581,16 @@ 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);
+       /*
+        * XXX.  Just for testing purposes, suppress the display of a toplevel
+        * gather node, so that we can run the regression tests with Gather
+        * nodes forcibly inserted without getting test failures due to different
+        * EXPLAIN output.
+        */
+       ps = queryDesc->planstate;
+       if (IsA(ps, GatherState))
+               ps = outerPlanState(ps);
+       ExplainNode(ps, NIL, NULL, NULL, es);
 }
 
 /*
index b487c002a8cea249f94c75bcf3e586a8075728c3..b0042a108b8a28e55ca5c0c3a2124ef27b8055f6 100644 (file)
@@ -1826,6 +1826,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 bdac0b1860b5ca2d8e8ba37e65189a6cdebc5747..ed0632984d3f14e7639fb5193650fed1ac005645 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");
index a09b4b5b4798b1921b05da248946025c523a0e1b..1204b42ce2315300426e65237f4e6c13ed9ccf81 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;
+bool           force_parallel_mode = false;
 
 /* Hook for plugins to get control in planner() */
 planner_hook_type planner_hook = NULL;
@@ -230,25 +232,36 @@ 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 set, 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 || !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);
+       }
+
+       /*
+        * We might flip this from true to false later on during planning, but
+        * never the other direction.
+        */
+       glob->wholePlanParallelSafe = glob->parallelModeOK;
 
        /* Determine what fraction of the plan is likely to be scanned */
        if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -292,6 +305,33 @@ 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)
+       {
+               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;
+               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 d949c19ca2f32414c756a1d8eab65a621e709e0d..c25656ad2e6a4c176c109fb61caddb3d65bbebd8 100644 (file)
@@ -863,6 +863,17 @@ static struct config_bool ConfigureNamesBool[] =
                true,
                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,
+               false,
+               NULL, NULL, NULL
+       },
+
        {
                {"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
                        gettext_noop("Enables genetic query optimization."),
index 930628c33bccd857a11337185c83f77ffec05407..06bc3fa51cdad09324c3d2161152f2d6cc087344 100644 (file)
 #from_collapse_limit = 8
 #join_collapse_limit = 8               # 1 disables collapsing of explicit
                                        # JOIN clauses
+#force_parallel_mode = off
 
 
 #------------------------------------------------------------------------------
index 94925984bf2d8dd17e3228a2d5c2ab2de9c139e2..5c22679afa5834313f3e12164bb5960d93a0b8d8 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..f38f017933438bc55c14ba36a6312fa716cbc840 100644 (file)
@@ -20,6 +20,7 @@
 /* GUC parameters */
 #define DEFAULT_CURSOR_TUPLE_FRACTION 0.1
 extern double cursor_tuple_fraction;
+extern bool force_parallel_mode;
 
 /* query_planner callback to compute query_pathkeys */
 typedef void (*query_pathkeys_callback) (PlannerInfo *root, void *extra);