Skip to content
Draft
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f7f815a
Fix wrong result in parallel ChunkAppend
akuzm Mar 10, 2026
ac1bda3
format
akuzm Mar 10, 2026
c8d7535
Fix parallel ChunkAppend test to use non-partitioning column
Mar 10, 2026
8167dd5
Execute parallel mixed partial/non-partial plans
claude Mar 10, 2026
34295fe
Add test for runtime exclusion with mixed MergeAppend/scan children
claude Mar 10, 2026
a327405
Move runtime exclusion test from shared to non-shared suite
claude Mar 10, 2026
35b705a
Merge origin/main into pr9388 (using imerge)
claude May 4, 2026
0a28b19
Stabilize chunk_append_space_runtime_exclusion across PG versions
claude May 4, 2026
0dc3695
Merge branch 'main' into exclude-chunk
akuzm May 6, 2026
ba20237
Merge commit '903aaf030757f6f8e7351ae6faf81c829f92e96d' into HEAD
akuzm May 13, 2026
edd10a5
Merge commit 'a93f871c11b687b4cf268ede5cf80368f3231759' into HEAD
akuzm May 13, 2026
cc32de0
cleanup
akuzm May 13, 2026
893bb82
Merge branch 'main' into exclude-chunk
akuzm May 18, 2026
26a1ba9
tmp
akuzm May 20, 2026
cc33fa7
revert?
akuzm May 20, 2026
fa02504
Merge branch 'main' into exclude-chunk
akuzm Jun 1, 2026
7f93049
Merge branch 'main' into exclude-chunk
akuzm Jun 5, 2026
351285d
Merge branch 'main' into exclude-chunk
akuzm Jun 9, 2026
8f7c46b
Merge remote-tracking branch 'origin/main' into HEAD
akuzm Jun 23, 2026
ed4375e
Merge branch 'main' into exclude-chunk
akuzm Jun 24, 2026
6b36cf4
Merge branch 'main' into exclude-chunk
akuzm Jul 6, 2026
a9aab4a
Merge branch 'main' into exclude-chunk
akuzm Jul 17, 2026
3b8597a
Merge branch 'main' into exclude-chunk
akuzm Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
418 changes: 152 additions & 266 deletions src/nodes/chunk_append/exec.c

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions src/nodes/chunk_append/planner.c
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,12 @@ ts_chunk_append_plan_create(PlannerInfo *root, RelOptInfo *rel, CustomPath *path
cscan->custom_plans = custom_plans;

/*
* If we do either startup or runtime exclusion, we need to pass restrictinfo
* clauses into executor.
* Always pass per-chunk restrictinfo clauses and RT indexes into the
* executor so that the three per-chunk lists (subplans, ri_clauses,
* constraints) are uniformly populated. This is required for parallel
* workers that index into these lists, and keeps startup/runtime
* exclusion from having to cope with missing data.
*/
if (capath->startup_exclusion || capath->runtime_exclusion_children)
{
foreach (lc_child, cscan->custom_plans)
{
Expand Down
43 changes: 43 additions & 0 deletions test/expected/parallel-15.out
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,11 @@ ALTER TABLE :CHUNK2 SET (parallel_workers=2);
One-Time Filter: (length(version()) > 0)
-> Parallel Seq Scan on _hyper_1_2_chunk

SELECT count(*) FROM "test" WHERE i > 400000 AND length(version()) > 0;
count
-------
59999

ALTER TABLE :CHUNK1 SET (parallel_workers=2);
ALTER TABLE :CHUNK2 SET (parallel_workers=0);
:PREFIX SELECT count(*) FROM "test" WHERE i < 600000 AND length(version()) > 0;
Expand All @@ -623,6 +628,11 @@ ALTER TABLE :CHUNK2 SET (parallel_workers=0);
One-Time Filter: (length(version()) > 0)
-> Parallel Seq Scan on _hyper_1_1_chunk

SELECT count(*) FROM "test" WHERE i < 600000 AND length(version()) > 0;
count
-------
60000

-- Verify result correctness with mixed partial/non-partial subplans.
-- chunk1 is partial (parallel_workers=2), chunk2 is non-partial (parallel_workers=0).
SET max_parallel_workers_per_gather TO 0;
Expand Down Expand Up @@ -757,3 +767,36 @@ RESET max_parallel_workers_per_gather;
-> Seq Scan on _hyper_1_2_chunk
Filter: (ts < now())

-- test parallel ChunkAppend with InitPlan params (runtime exclusion)
-- Filter on j (non-partitioning column) so that the planner produces a
-- parallel-aware ChunkAppend with runtime_exclusion_parent but without
-- startup_exclusion. Filtering on the partitioning column i would enable
-- runtime_exclusion_children, making the planner pick Single Copy mode
-- (non-parallel ChunkAppend) which does not exercise the shared-memory
-- subplan coordination path.
SET max_parallel_workers_per_gather = 2;
SET parallel_tuple_cost = 0;
SET parallel_setup_cost = 0;
SET min_parallel_table_scan_size = 0;
-- get the sequential result for comparison
SET max_parallel_workers_per_gather = 0;
SELECT count(*) AS expected FROM "test" WHERE j = (SELECT max(j) FROM "test") \gset
-- parallel with leader participation
SET max_parallel_workers_per_gather = 2;
SELECT count(*) = :expected AS leader_ok FROM "test" WHERE j = (SELECT max(j) FROM "test");
leader_ok
-----------
t

-- parallel with only workers (leader does not participate)
SET parallel_leader_participation = off;
SELECT count(*) = :expected AS workers_ok FROM "test" WHERE j = (SELECT max(j) FROM "test");
workers_ok
------------
t

RESET parallel_leader_participation;
RESET max_parallel_workers_per_gather;
RESET parallel_tuple_cost;
RESET parallel_setup_cost;
RESET min_parallel_table_scan_size;
43 changes: 43 additions & 0 deletions test/expected/parallel-16.out
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,11 @@ ALTER TABLE :CHUNK2 SET (parallel_workers=2);
One-Time Filter: (length(version()) > 0)
-> Parallel Seq Scan on _hyper_1_2_chunk

SELECT count(*) FROM "test" WHERE i > 400000 AND length(version()) > 0;
count
-------
59999

ALTER TABLE :CHUNK1 SET (parallel_workers=2);
ALTER TABLE :CHUNK2 SET (parallel_workers=0);
:PREFIX SELECT count(*) FROM "test" WHERE i < 600000 AND length(version()) > 0;
Expand All @@ -625,6 +630,11 @@ ALTER TABLE :CHUNK2 SET (parallel_workers=0);
One-Time Filter: (length(version()) > 0)
-> Parallel Seq Scan on _hyper_1_1_chunk

SELECT count(*) FROM "test" WHERE i < 600000 AND length(version()) > 0;
count
-------
60000

-- Verify result correctness with mixed partial/non-partial subplans.
-- chunk1 is partial (parallel_workers=2), chunk2 is non-partial (parallel_workers=0).
SET max_parallel_workers_per_gather TO 0;
Expand Down Expand Up @@ -759,3 +769,36 @@ RESET max_parallel_workers_per_gather;
-> Seq Scan on _hyper_1_2_chunk
Filter: (ts < now())

-- test parallel ChunkAppend with InitPlan params (runtime exclusion)
-- Filter on j (non-partitioning column) so that the planner produces a
-- parallel-aware ChunkAppend with runtime_exclusion_parent but without
-- startup_exclusion. Filtering on the partitioning column i would enable
-- runtime_exclusion_children, making the planner pick Single Copy mode
-- (non-parallel ChunkAppend) which does not exercise the shared-memory
-- subplan coordination path.
SET max_parallel_workers_per_gather = 2;
SET parallel_tuple_cost = 0;
SET parallel_setup_cost = 0;
SET min_parallel_table_scan_size = 0;
-- get the sequential result for comparison
SET max_parallel_workers_per_gather = 0;
SELECT count(*) AS expected FROM "test" WHERE j = (SELECT max(j) FROM "test") \gset
-- parallel with leader participation
SET max_parallel_workers_per_gather = 2;
SELECT count(*) = :expected AS leader_ok FROM "test" WHERE j = (SELECT max(j) FROM "test");
leader_ok
-----------
t

-- parallel with only workers (leader does not participate)
SET parallel_leader_participation = off;
SELECT count(*) = :expected AS workers_ok FROM "test" WHERE j = (SELECT max(j) FROM "test");
workers_ok
------------
t

RESET parallel_leader_participation;
RESET max_parallel_workers_per_gather;
RESET parallel_tuple_cost;
RESET parallel_setup_cost;
RESET min_parallel_table_scan_size;
43 changes: 43 additions & 0 deletions test/expected/parallel-17.out
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,11 @@ ALTER TABLE :CHUNK2 SET (parallel_workers=2);
One-Time Filter: (length(version()) > 0)
-> Parallel Seq Scan on _hyper_1_2_chunk

SELECT count(*) FROM "test" WHERE i > 400000 AND length(version()) > 0;
count
-------
59999

ALTER TABLE :CHUNK1 SET (parallel_workers=2);
ALTER TABLE :CHUNK2 SET (parallel_workers=0);
:PREFIX SELECT count(*) FROM "test" WHERE i < 600000 AND length(version()) > 0;
Expand All @@ -625,6 +630,11 @@ ALTER TABLE :CHUNK2 SET (parallel_workers=0);
One-Time Filter: (length(version()) > 0)
-> Parallel Seq Scan on _hyper_1_1_chunk

SELECT count(*) FROM "test" WHERE i < 600000 AND length(version()) > 0;
count
-------
60000

-- Verify result correctness with mixed partial/non-partial subplans.
-- chunk1 is partial (parallel_workers=2), chunk2 is non-partial (parallel_workers=0).
SET max_parallel_workers_per_gather TO 0;
Expand Down Expand Up @@ -759,3 +769,36 @@ RESET max_parallel_workers_per_gather;
-> Seq Scan on _hyper_1_2_chunk
Filter: (ts < now())

-- test parallel ChunkAppend with InitPlan params (runtime exclusion)
-- Filter on j (non-partitioning column) so that the planner produces a
-- parallel-aware ChunkAppend with runtime_exclusion_parent but without
-- startup_exclusion. Filtering on the partitioning column i would enable
-- runtime_exclusion_children, making the planner pick Single Copy mode
-- (non-parallel ChunkAppend) which does not exercise the shared-memory
-- subplan coordination path.
SET max_parallel_workers_per_gather = 2;
SET parallel_tuple_cost = 0;
SET parallel_setup_cost = 0;
SET min_parallel_table_scan_size = 0;
-- get the sequential result for comparison
SET max_parallel_workers_per_gather = 0;
SELECT count(*) AS expected FROM "test" WHERE j = (SELECT max(j) FROM "test") \gset
-- parallel with leader participation
SET max_parallel_workers_per_gather = 2;
SELECT count(*) = :expected AS leader_ok FROM "test" WHERE j = (SELECT max(j) FROM "test");
leader_ok
-----------
t

-- parallel with only workers (leader does not participate)
SET parallel_leader_participation = off;
SELECT count(*) = :expected AS workers_ok FROM "test" WHERE j = (SELECT max(j) FROM "test");
workers_ok
------------
t

RESET parallel_leader_participation;
RESET max_parallel_workers_per_gather;
RESET parallel_tuple_cost;
RESET parallel_setup_cost;
RESET min_parallel_table_scan_size;
43 changes: 43 additions & 0 deletions test/expected/parallel-18.out
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,11 @@ ALTER TABLE :CHUNK2 SET (parallel_workers=2);
One-Time Filter: (length(version()) > 0)
-> Parallel Seq Scan on _hyper_1_2_chunk

SELECT count(*) FROM "test" WHERE i > 400000 AND length(version()) > 0;
count
-------
59999

ALTER TABLE :CHUNK1 SET (parallel_workers=2);
ALTER TABLE :CHUNK2 SET (parallel_workers=0);
:PREFIX SELECT count(*) FROM "test" WHERE i < 600000 AND length(version()) > 0;
Expand All @@ -598,6 +603,11 @@ ALTER TABLE :CHUNK2 SET (parallel_workers=0);
One-Time Filter: (length(version()) > 0)
-> Parallel Seq Scan on _hyper_1_1_chunk

SELECT count(*) FROM "test" WHERE i < 600000 AND length(version()) > 0;
count
-------
60000

-- Verify result correctness with mixed partial/non-partial subplans.
-- chunk1 is partial (parallel_workers=2), chunk2 is non-partial (parallel_workers=0).
SET max_parallel_workers_per_gather TO 0;
Expand Down Expand Up @@ -732,3 +742,36 @@ RESET max_parallel_workers_per_gather;
-> Seq Scan on _hyper_1_2_chunk
Filter: (ts < now())

-- test parallel ChunkAppend with InitPlan params (runtime exclusion)
-- Filter on j (non-partitioning column) so that the planner produces a
-- parallel-aware ChunkAppend with runtime_exclusion_parent but without
-- startup_exclusion. Filtering on the partitioning column i would enable
-- runtime_exclusion_children, making the planner pick Single Copy mode
-- (non-parallel ChunkAppend) which does not exercise the shared-memory
-- subplan coordination path.
SET max_parallel_workers_per_gather = 2;
SET parallel_tuple_cost = 0;
SET parallel_setup_cost = 0;
SET min_parallel_table_scan_size = 0;
-- get the sequential result for comparison
SET max_parallel_workers_per_gather = 0;
SELECT count(*) AS expected FROM "test" WHERE j = (SELECT max(j) FROM "test") \gset
-- parallel with leader participation
SET max_parallel_workers_per_gather = 2;
SELECT count(*) = :expected AS leader_ok FROM "test" WHERE j = (SELECT max(j) FROM "test");
leader_ok
-----------
t

-- parallel with only workers (leader does not participate)
SET parallel_leader_participation = off;
SELECT count(*) = :expected AS workers_ok FROM "test" WHERE j = (SELECT max(j) FROM "test");
workers_ok
------------
t

RESET parallel_leader_participation;
RESET max_parallel_workers_per_gather;
RESET parallel_tuple_cost;
RESET parallel_setup_cost;
RESET min_parallel_table_scan_size;
32 changes: 32 additions & 0 deletions test/sql/parallel.sql.in
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,12 @@ RESET max_parallel_workers_per_gather;
ALTER TABLE :CHUNK1 SET (parallel_workers=0);
ALTER TABLE :CHUNK2 SET (parallel_workers=2);
:PREFIX SELECT count(*) FROM "test" WHERE i > 400000 AND length(version()) > 0;
SELECT count(*) FROM "test" WHERE i > 400000 AND length(version()) > 0;

ALTER TABLE :CHUNK1 SET (parallel_workers=2);
ALTER TABLE :CHUNK2 SET (parallel_workers=0);
:PREFIX SELECT count(*) FROM "test" WHERE i < 600000 AND length(version()) > 0;
SELECT count(*) FROM "test" WHERE i < 600000 AND length(version()) > 0;

-- Verify result correctness with mixed partial/non-partial subplans.
-- chunk1 is partial (parallel_workers=2), chunk2 is non-partial (parallel_workers=0).
Expand Down Expand Up @@ -240,3 +242,33 @@ RESET max_parallel_workers_per_gather;
-- this won't be parallel query because now() is parallel restricted in PG < 12
:PREFIX SELECT i FROM "test" WHERE ts < now();

-- test parallel ChunkAppend with InitPlan params (runtime exclusion)
-- Filter on j (non-partitioning column) so that the planner produces a
-- parallel-aware ChunkAppend with runtime_exclusion_parent but without
-- startup_exclusion. Filtering on the partitioning column i would enable
-- runtime_exclusion_children, making the planner pick Single Copy mode
-- (non-parallel ChunkAppend) which does not exercise the shared-memory
-- subplan coordination path.
SET max_parallel_workers_per_gather = 2;
SET parallel_tuple_cost = 0;
SET parallel_setup_cost = 0;
SET min_parallel_table_scan_size = 0;

-- get the sequential result for comparison
SET max_parallel_workers_per_gather = 0;
SELECT count(*) AS expected FROM "test" WHERE j = (SELECT max(j) FROM "test") \gset

-- parallel with leader participation
SET max_parallel_workers_per_gather = 2;
SELECT count(*) = :expected AS leader_ok FROM "test" WHERE j = (SELECT max(j) FROM "test");

-- parallel with only workers (leader does not participate)
SET parallel_leader_participation = off;
SELECT count(*) = :expected AS workers_ok FROM "test" WHERE j = (SELECT max(j) FROM "test");
RESET parallel_leader_participation;

RESET max_parallel_workers_per_gather;
RESET parallel_tuple_cost;
RESET parallel_setup_cost;
RESET min_parallel_table_scan_size;

Loading
Loading