Thread
-
[PATCH v0 1/1] Revisit Subplan's parallel safety.
Andy Fan <zhihuifan1213@163.com> — 2025-07-02T06:28:02Z
--- src/backend/optimizer/plan/planner.c | 2 + src/backend/optimizer/plan/subselect.c | 2 + src/backend/optimizer/util/clauses.c | 32 ++++++++++----- src/include/nodes/parsenodes.h | 9 ++++ src/include/nodes/pathnodes.h | 3 ++ .../regress/expected/incremental_sort.out | 41 ++++++++++--------- src/test/regress/expected/select_parallel.out | 26 +++++++----- 7 files changed, 73 insertions(+), 42 deletions(-) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 549aedcfa99..3a1fa618e4f 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -708,6 +708,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root, */ root->join_domains = list_make1(makeNode(JoinDomain)); + root->isSubPlan = parse->is_in_sublink; + /* * If there is a WITH list, process each WITH query and either convert it * to RTE_SUBQUERY RTE(s) or build an initplan SubPlan structure for it. diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index e7cb3fede66..4de880de9ef 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -181,6 +181,8 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, */ subquery = copyObject(orig_subquery); + subquery->is_in_sublink = true; + /* * If it's an EXISTS subplan, we might be able to simplify it. */ diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 26a3e050086..63816d8a733 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -92,6 +92,7 @@ typedef struct char max_hazard; /* worst proparallel hazard found so far */ char max_interesting; /* worst proparallel hazard of interest */ List *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */ + bool is_subplan; } max_parallel_hazard_context; static bool contain_agg_clause_walker(Node *node, void *context); @@ -770,23 +771,26 @@ is_parallel_safe(PlannerInfo *root, Node *node) context.max_hazard = PROPARALLEL_SAFE; context.max_interesting = PROPARALLEL_RESTRICTED; context.safe_param_ids = NIL; + context.is_subplan = root->isSubPlan; - /* - * The params that refer to the same or parent query level are considered - * parallel-safe. The idea is that we compute such params at Gather or - * Gather Merge node and pass their value to workers. - */ - for (proot = root; proot != NULL; proot = proot->parent_root) + if (!context.is_subplan) { - foreach(l, proot->init_plans) + /* + * The params that refer to the same or parent query level are + * considered parallel-safe. The idea is that we compute such params + * at Gather or Gather Merge node and pass their value to workers. + */ + for (proot = root; proot != NULL; proot = proot->parent_root) { - SubPlan *initsubplan = (SubPlan *) lfirst(l); + foreach(l, proot->init_plans) + { + SubPlan *initsubplan = (SubPlan *) lfirst(l); - context.safe_param_ids = list_concat(context.safe_param_ids, - initsubplan->setParam); + context.safe_param_ids = list_concat(context.safe_param_ids, + initsubplan->setParam); + } } } - return !max_parallel_hazard_walker(node, &context); } @@ -936,6 +940,12 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context) if (param->paramkind == PARAM_EXTERN) return false; + /* + * Subplan is always non partial plan, so their parameter are always + * computed by leader. + */ + if (context->is_subplan) + return false; if (param->paramkind != PARAM_EXEC || !list_member_int(context->safe_param_ids, param->paramid)) { diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index daa285ca62f..cc36dc72eb3 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -244,6 +244,15 @@ typedef struct Query /* a list of WithCheckOption's (added during rewrite) */ List *withCheckOptions pg_node_attr(query_jumble_ignore); + /* + * XXX: looks currently we don't know where a Query comes from, e.g. + * SubQuery or SubLink. Providing such information to planner might be + * helpful sometimes. For now only SubLink case is interesting and it is + * only set during make_subplan. we can do it in parser if it worths the + * trouble (there are many members in enum SubLinkType). + */ + bool is_in_sublink; + /* * The following two fields identify the portion of the source text string * containing this query. They are typically only populated in top-level diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 6567759595d..053ace26e74 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -583,6 +583,9 @@ struct PlannerInfo /* PartitionPruneInfos added in this query's plan. */ List *partPruneInfos; + + /* is building a subplan */ + bool isSubPlan; }; diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out index b00219643b9..9ba232c4c67 100644 --- a/src/test/regress/expected/incremental_sort.out +++ b/src/test/regress/expected/incremental_sort.out @@ -1605,20 +1605,21 @@ explain (costs off) select distinct unique1, (select t.unique1 from tenk1 where tenk1.unique1 = t.unique1) from tenk1 t, generate_series(1, 1000); - QUERY PLAN ---------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------- Unique - -> Sort - Sort Key: t.unique1, ((SubPlan 1)) - -> Gather - Workers Planned: 2 - -> Nested Loop - -> Parallel Index Only Scan using tenk1_unique1 on tenk1 t - -> Function Scan on generate_series - SubPlan 1 - -> Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 = t.unique1) -(11 rows) + -> Gather Merge + Workers Planned: 2 + -> Unique + -> Sort + Sort Key: t.unique1, ((SubPlan 1)) + -> Nested Loop + -> Parallel Index Only Scan using tenk1_unique1 on tenk1 t + -> Function Scan on generate_series + SubPlan 1 + -> Index Only Scan using tenk1_unique1 on tenk1 + Index Cond: (unique1 = t.unique1) +(12 rows) explain (costs off) select unique1, @@ -1627,16 +1628,16 @@ from tenk1 t, generate_series(1, 1000) order by 1, 2; QUERY PLAN --------------------------------------------------------------------------- - Sort - Sort Key: t.unique1, ((SubPlan 1)) - -> Gather - Workers Planned: 2 + Gather Merge + Workers Planned: 2 + -> Sort + Sort Key: t.unique1, ((SubPlan 1)) -> Nested Loop -> Parallel Index Only Scan using tenk1_unique1 on tenk1 t -> Function Scan on generate_series - SubPlan 1 - -> Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 = t.unique1) + SubPlan 1 + -> Index Only Scan using tenk1_unique1 on tenk1 + Index Cond: (unique1 = t.unique1) (10 rows) -- Parallel sort but with expression not available until the upper rel. diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out index 0185ef661b1..189a77a4f5f 100644 --- a/src/test/regress/expected/select_parallel.out +++ b/src/test/regress/expected/select_parallel.out @@ -148,8 +148,8 @@ create table part_pa_test_p2 partition of part_pa_test for values from (0) to (m explain (costs off) select (select max((select pa1.b from part_pa_test pa1 where pa1.a = pa2.a))) from part_pa_test pa2; - QUERY PLAN --------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------- Aggregate -> Gather Workers Planned: 3 @@ -159,12 +159,14 @@ explain (costs off) SubPlan 2 -> Result SubPlan 1 - -> Append - -> Seq Scan on part_pa_test_p1 pa1_1 - Filter: (a = pa2.a) - -> Seq Scan on part_pa_test_p2 pa1_2 - Filter: (a = pa2.a) -(14 rows) + -> Gather + Workers Planned: 3 + -> Parallel Append + -> Parallel Seq Scan on part_pa_test_p1 pa1_1 + Filter: (a = pa2.a) + -> Parallel Seq Scan on part_pa_test_p2 pa1_2 + Filter: (a = pa2.a) +(16 rows) drop table part_pa_test; -- test with leader participation disabled @@ -1339,9 +1341,11 @@ SELECT 1 FROM tenk1_vw_sec -> Parallel Index Only Scan using tenk1_unique1 on tenk1 SubPlan 1 -> Aggregate - -> Seq Scan on int4_tbl - Filter: (f1 < tenk1_vw_sec.unique1) -(9 rows) + -> Gather + Workers Planned: 1 + -> Parallel Seq Scan on int4_tbl + Filter: (f1 < tenk1_vw_sec.unique1) +(11 rows) rollback; -- test that a newly-created session role propagates to workers. -- 2.45.1 --=-=-=--