diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 392eb700b0..de84b77730 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -3802,6 +3802,51 @@ SELECT * FROM parent WHERE key = 2400; + + force_parallel_mode (enum) + + force_parallel_mode configuration parameter + + + + + Allows the use of parallel queries for testing purposes even in cases + where no performance benefit is expected. + The allowed values of force_parallel_mode are + off (use parallel mode only when it is expected to improve + performance), on (force parallel query for all queries + for which it is thought to be safe), and regress (like + on, but with additional behavior changes to facilitate automated + regression testing). + + + + More specifically, setting this value to on will add + a 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 PARALLEL UNSAFE + (or, possibly, PARALLEL RESTRICTED). + + + + Setting this value to regress has all of the same effects + as setting it to 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 regress suppresses this to guarantee + reproducible results. Also, the Gather nodes added to + plans by this setting are hidden from the EXPLAIN output + so that the output matches what would be obtained if this setting + were turned off. + + + + diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c index bf2e691f57..4f91cd0265 100644 --- a/src/backend/access/transam/parallel.c +++ b/src/backend/access/transam/parallel.c @@ -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); } /* diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 25d8ca075d..ee13136b7f 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -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); } /* diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index a8b79fa8c3..e54d1744b0 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -334,6 +334,7 @@ _copyGather(const Gather *from) */ COPY_SCALAR_FIELD(num_workers); COPY_SCALAR_FIELD(single_copy); + COPY_SCALAR_FIELD(invisible); return newnode; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index d59b954654..3e1c3e6be5 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -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); } diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 6c461513d6..e4d41ee95b 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -2053,6 +2053,7 @@ _readGather(void) READ_INT_FIELD(num_workers); READ_BOOL_FIELD(single_copy); + READ_BOOL_FIELD(invisible); READ_DONE(); } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 54ff7f623d..6e0db08038 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -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; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index a09b4b5b47..a3cc27464c 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -48,10 +48,12 @@ #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 diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 66c479141f..31a69cac72 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -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 diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 029114fc22..09b2003dbe 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -313,6 +313,7 @@ #from_collapse_limit = 8 #join_collapse_limit = 8 # 1 disables collapsing of explicit # JOIN clauses +#force_parallel_mode = off #------------------------------------------------------------------------------ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 55d6bbe8f0..ae224cfa31 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -775,6 +775,7 @@ typedef struct Gather Plan plan; int num_workers; bool single_copy; + bool invisible; /* suppress EXPLAIN display (for testing)? */ } Gather; /* ---------------- diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 595438cb24..96198aeec1 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -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; diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 7ae73676e8..eaa642bc57 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -17,9 +17,18 @@ #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);