BUG #19684: Assertion in tuplesort_begin_heap() falsified by parallel plan with sort

Started by PG Bug reporting form9 days ago5 messagesbugs
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliessuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253773
psql -h localhost -U postgres

Built from patchset v5 (message #5), September 20, 2026 at 10:02 AM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253773_5 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253773_5 && git checkout t253773_5

Patchset v5 (message #5) is on t253773_5

Jump to latest
#1PG Bug reporting form
noreply@postgresql.org

The following bug has been logged on the website:

Bug reference: 19684
Logged by: Alexander Lakhin
Email address: exclusion@gmail.com
PostgreSQL version: 19beta3
Operating system: Ubuntu 24.04
Description:

The following script:
SET cpu_tuple_cost = 1000;
SET min_parallel_table_scan_size = 1;

CREATE TABLE t(i int);
SELECT FROM t UNION SELECT FROM t;

triggers:
TRAP: failed Assert("nkeys > 0"), File: "tuplesortvariants.c", Line: 195,
PID: 1465852

EXPLAIN shows:
QUERY PLAN
-----------------------------------------------------------------------------------------------
Unique (cost=3188844.07..3188856.82 rows=2 width=0)
-> Sort (cost=3188844.07..3188856.82 rows=5100 width=0)
-> Gather (cost=1000.00..3188530.00 rows=5100 width=0)
Workers Planned: 2
-> Parallel Append (cost=0.00..3187020.00 rows=2124
width=0)
-> Parallel Seq Scan on t (cost=0.00..1062510.00
rows=1062 width=0)
-> Parallel Seq Scan on t t_1 (cost=0.00..1062510.00
rows=1062 width=0)

Without asserts enabled, SELECT succeeds and EXPLAIN (ANALYZE) shows the
same plan.

Reproduced starting from 66c0185a3/12933dc60.

#2Ayush Tiwari
ayushtiwari.slg01@gmail.com
In reply to: PG Bug reporting form (#1)
Re: BUG #19684: Assertion in tuplesort_begin_heap() falsified by parallel plan with sort

Hi,

On Sat, 12 Sept 2026 at 20:33, PG Bug reporting form
<noreply@postgresql.org> wrote:

The following bug has been logged on the website:

Bug reference: 19684
Logged by: Alexander Lakhin
Email address: exclusion@gmail.com
PostgreSQL version: 19beta3
Operating system: Ubuntu 24.04
Description:

The following script:
SET cpu_tuple_cost = 1000;
SET min_parallel_table_scan_size = 1;

CREATE TABLE t(i int);
SELECT FROM t UNION SELECT FROM t;

triggers:
TRAP: failed Assert("nkeys > 0"), File: "tuplesortvariants.c", Line: 195,
PID: 1465852

EXPLAIN shows:
QUERY PLAN
-----------------------------------------------------------------------------------------------
Unique (cost=3188844.07..3188856.82 rows=2 width=0)
-> Sort (cost=3188844.07..3188856.82 rows=5100 width=0)
-> Gather (cost=1000.00..3188530.00 rows=5100 width=0)
Workers Planned: 2
-> Parallel Append (cost=0.00..3187020.00 rows=2124
width=0)
-> Parallel Seq Scan on t (cost=0.00..1062510.00
rows=1062 width=0)
-> Parallel Seq Scan on t t_1 (cost=0.00..1062510.00
rows=1062 width=0)

Without asserts enabled, SELECT succeeds and EXPLAIN (ANALYZE) shows the
same plan.

Reproduced starting from 66c0185a3/12933dc60.

Thanks for the report!

It looks like the Gather path is missing the check already used for the
non-parallel Append path. For a zero-column UNION, groupList is NIL, but
the Gather path still creates a Sort and eventually calls
tuplesort_begin_heap() with zero sort keys.

I added the same "if (groupList != NIL)" condition around
create_sort_path() for the Gather path. With the patch, the reported test
and a variant using a populated table both complete successfully.

I wonder if we should also add an Assert(pathkeys != NIL) inside
create_sort_path()?

Regards,
Ayush

Attachments:

t253773_2
v1-0001-Fix-zero-column-UNION-with-parallel-plans.patchapplication/octet-stream; name=v1-0001-Fix-zero-column-UNION-with-parallel-plans.patchDownload+34-2
#3Samriddha Kumar Tripathi
sumitkumartripathi0@gmail.com
In reply to: Ayush Tiwari (#2)
Re: BUG #19684: Assertion in tuplesort_begin_heap() falsified by parallel plan with sort

Hi Ayush,

Thanks for the patch. I applied it and tested against the repro:
On unpatched master (with --enable-cassert), it crashes with the exact
reported assertion (Assert("nkeys > 0") in tuplesortvariants.c:195). With
your patch applied, the same query completes cleanly with (0 rows), no
crash.

On the Assert(pathkeys != NIL) question:
I tested it directly: with your guard temporarily removed but the assert
added to create_sort_path(), the same bug is caught immediately at
pathnode.c:2915 instead of downstream at tuplesortvariants.c:195. With the
guard back in place and the assert added, all 240 regression tests pass.

So the assert is safe to add, and it would make failures easier to diagnose
if some future caller makes the same mistake. That said, I'm not sure if
it's necessary, your guard already fixes the actual bug at its source, and
this call site is the only one that had the problem.

Regards,
Samriddha

On Sat, Sep 12, 2026 at 10:15 PM Ayush Tiwari <ayushtiwari.slg01@gmail.com>
wrote:

Show quoted text

Hi,

On Sat, 12 Sept 2026 at 20:33, PG Bug reporting form
<noreply@postgresql.org> wrote:

The following bug has been logged on the website:

Bug reference: 19684
Logged by: Alexander Lakhin
Email address: exclusion@gmail.com
PostgreSQL version: 19beta3
Operating system: Ubuntu 24.04
Description:

The following script:
SET cpu_tuple_cost = 1000;
SET min_parallel_table_scan_size = 1;

CREATE TABLE t(i int);
SELECT FROM t UNION SELECT FROM t;

triggers:
TRAP: failed Assert("nkeys > 0"), File: "tuplesortvariants.c", Line: 195,
PID: 1465852

EXPLAIN shows:
QUERY PLAN

-----------------------------------------------------------------------------------------------

Unique (cost=3188844.07..3188856.82 rows=2 width=0)
-> Sort (cost=3188844.07..3188856.82 rows=5100 width=0)
-> Gather (cost=1000.00..3188530.00 rows=5100 width=0)
Workers Planned: 2
-> Parallel Append (cost=0.00..3187020.00 rows=2124
width=0)
-> Parallel Seq Scan on t (cost=0.00..1062510.00
rows=1062 width=0)
-> Parallel Seq Scan on t t_1

(cost=0.00..1062510.00

rows=1062 width=0)

Without asserts enabled, SELECT succeeds and EXPLAIN (ANALYZE) shows the
same plan.

Reproduced starting from 66c0185a3/12933dc60.

Thanks for the report!

It looks like the Gather path is missing the check already used for the
non-parallel Append path. For a zero-column UNION, groupList is NIL, but
the Gather path still creates a Sort and eventually calls
tuplesort_begin_heap() with zero sort keys.

I added the same "if (groupList != NIL)" condition around
create_sort_path() for the Gather path. With the patch, the reported test
and a variant using a populated table both complete successfully.

I wonder if we should also add an Assert(pathkeys != NIL) inside
create_sort_path()?

Regards,
Ayush

#4Ayush Tiwari
ayushtiwari.slg01@gmail.com
In reply to: Ayush Tiwari (#2)
Re: BUG #19684: Assertion in tuplesort_begin_heap() falsified by parallel plan with sort

Hi,

On Sat, 12 Sept 2026 at 22:15, Ayush Tiwari <ayushtiwari.slg01@gmail.com> wrote:

Hi,

On Sat, 12 Sept 2026 at 20:33, PG Bug reporting form
<noreply@postgresql.org> wrote:

The following bug has been logged on the website:

Bug reference: 19684
Logged by: Alexander Lakhin
Email address: exclusion@gmail.com
PostgreSQL version: 19beta3
Operating system: Ubuntu 24.04
Description:

The following script:
SET cpu_tuple_cost = 1000;
SET min_parallel_table_scan_size = 1;

CREATE TABLE t(i int);
SELECT FROM t UNION SELECT FROM t;

triggers:
TRAP: failed Assert("nkeys > 0"), File: "tuplesortvariants.c", Line: 195,
PID: 1465852

EXPLAIN shows:
QUERY PLAN
-----------------------------------------------------------------------------------------------
Unique (cost=3188844.07..3188856.82 rows=2 width=0)
-> Sort (cost=3188844.07..3188856.82 rows=5100 width=0)
-> Gather (cost=1000.00..3188530.00 rows=5100 width=0)
Workers Planned: 2
-> Parallel Append (cost=0.00..3187020.00 rows=2124
width=0)
-> Parallel Seq Scan on t (cost=0.00..1062510.00
rows=1062 width=0)
-> Parallel Seq Scan on t t_1 (cost=0.00..1062510.00
rows=1062 width=0)

Without asserts enabled, SELECT succeeds and EXPLAIN (ANALYZE) shows the
same plan.

Reproduced starting from 66c0185a3/12933dc60.

Thanks for the report!

It looks like the Gather path is missing the check already used for the
non-parallel Append path. For a zero-column UNION, groupList is NIL, but
the Gather path still creates a Sort and eventually calls
tuplesort_begin_heap() with zero sort keys.

I added the same "if (groupList != NIL)" condition around
create_sort_path() for the Gather path. With the patch, the reported test
and a variant using a populated table both complete successfully.

I wonder if we should also add an Assert(pathkeys != NIL) inside
create_sort_path()?

Attached v2.

The code change is unchanged. I adjusted the regression query because v1
caused unrelated Gather advice warnings under test_plan_advice.

Regards,
Ayush

Attachments:

t253773_4
v2-0001-Fix-zero-column-UNION-with-parallel-plans.patchapplication/octet-stream; name=v2-0001-Fix-zero-column-UNION-with-parallel-plans.patchDownload+32-2
#5Ayush Tiwari
ayushtiwari.slg01@gmail.com
In reply to: Ayush Tiwari (#4)
Re: BUG #19684: Assertion in tuplesort_begin_heap() falsified by parallel plan with sort

On Sun, 13 Sept 2026 at 12:06, Ayush Tiwari <ayushtiwari.slg01@gmail.com> wrote:

Hi,

On Sat, 12 Sept 2026 at 22:15, Ayush Tiwari <ayushtiwari.slg01@gmail.com> wrote:

Hi,

On Sat, 12 Sept 2026 at 20:33, PG Bug reporting form
<noreply@postgresql.org> wrote:

The following bug has been logged on the website:

Bug reference: 19684
Logged by: Alexander Lakhin
Email address: exclusion@gmail.com
PostgreSQL version: 19beta3
Operating system: Ubuntu 24.04
Description:

The following script:
SET cpu_tuple_cost = 1000;
SET min_parallel_table_scan_size = 1;

CREATE TABLE t(i int);
SELECT FROM t UNION SELECT FROM t;

triggers:
TRAP: failed Assert("nkeys > 0"), File: "tuplesortvariants.c", Line: 195,
PID: 1465852

EXPLAIN shows:
QUERY PLAN
-----------------------------------------------------------------------------------------------
Unique (cost=3188844.07..3188856.82 rows=2 width=0)
-> Sort (cost=3188844.07..3188856.82 rows=5100 width=0)
-> Gather (cost=1000.00..3188530.00 rows=5100 width=0)
Workers Planned: 2
-> Parallel Append (cost=0.00..3187020.00 rows=2124
width=0)
-> Parallel Seq Scan on t (cost=0.00..1062510.00
rows=1062 width=0)
-> Parallel Seq Scan on t t_1 (cost=0.00..1062510.00
rows=1062 width=0)

Without asserts enabled, SELECT succeeds and EXPLAIN (ANALYZE) shows the
same plan.

Reproduced starting from 66c0185a3/12933dc60.

Thanks for the report!

It looks like the Gather path is missing the check already used for the
non-parallel Append path. For a zero-column UNION, groupList is NIL, but
the Gather path still creates a Sort and eventually calls
tuplesort_begin_heap() with zero sort keys.

I added the same "if (groupList != NIL)" condition around
create_sort_path() for the Gather path. With the patch, the reported test
and a variant using a populated table both complete successfully.

I wonder if we should also add an Assert(pathkeys != NIL) inside
create_sort_path()?

Attached is v3 with a minor correction to the regression test, so that
it exercises the affected path. The code change is unchanged.

Regards,
Ayush

Attachments:

t253773_5
v3-0001-Fix-zero-column-UNION-with-parallel-plans.patchapplication/octet-stream; name=v3-0001-Fix-zero-column-UNION-with-parallel-plans.patchDownload+40-2