| From: | PG Bug reporting form <noreply(at)postgresql(dot)org> |
|---|---|
| To: | pgsql-bugs(at)lists(dot)postgresql(dot)org |
| Cc: | 1548140642(at)qq(dot)com |
| Subject: | BUG #19882: Parallel HashAggregate loses worker spill statistics after Gather rescan |
| Date: | 2026-11-11 13:53:46 |
| Message-ID: | 19882-1ff869bb8e72439e@postgresql.org |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-bugs |
The following bug has been logged on the website:
Bug reference: 19882
Logged by: Milo
Email address: 1548140642(at)qq(dot)com
PostgreSQL version: 18.6
Operating system: Linux/amd64
Description:
When a rescannable parallel subtree executes a spilling Partial
HashAggregate and then executes a much smaller scan, EXPLAIN ANALYZE reports
only the last generation's per-worker spill statistics. In the recorded
case, Worker 0 initially used 33 batches and 22288kB of disk, but the
two-pass plan reports one batch and no disk usage for that worker.
Per-worker instrumentation should accumulate statistics across worker
generations that reuse the same slot. The query results remain correct; the
defect is in EXPLAIN's instrumentation.
How to repeat:
Save the following as reproducer.sql and run `psql -X -f reproducer.sql`:
```sql
DROP TABLE IF EXISTS hashagg_data;
CREATE TABLE hashagg_data AS
SELECT value AS item, value % 10000 AS bucket
FROM generate_series(1, 1000000) AS value;
ALTER TABLE hashagg_data SET (parallel_workers = 2);
ANALYZE hashagg_data;
SET max_parallel_workers_per_gather = 2;
SET max_parallel_workers = 1;
SET parallel_leader_participation = off;
SET parallel_setup_cost = 0;
SET parallel_tuple_cost = 0;
SET min_parallel_table_scan_size = 0;
SET work_mem = '64kB';
SET hash_mem_multiplier = 2;
SET enable_sort = off;
SELECT 'BASELINE_BEGIN';
EXPLAIN (ANALYZE, VERBOSE, COSTS OFF, TIMING OFF, SUMMARY OFF, BUFFERS OFF)
SELECT count(*)
FROM
(SELECT bucket, count(*)
FROM hashagg_data
WHERE item <= 1000000
GROUP BY bucket) AS grouped;
SELECT 'BASELINE_END';
SELECT 'RESCAN_BEGIN';
EXPLAIN (ANALYZE, VERBOSE, COSTS OFF, TIMING OFF, SUMMARY OFF, BUFFERS OFF)
SELECT limits.maximum,
(SELECT count(*)
FROM
(SELECT bucket, count(*)
FROM hashagg_data
WHERE item <= (SELECT limits.maximum)
GROUP BY bucket) AS grouped)
FROM (VALUES (1000000), (10)) AS limits(maximum);
SELECT 'RESCAN_END';
DROP TABLE hashagg_data;
```
The baseline contains:
```
-> Partial HashAggregate (actual rows=10000.00 loops=1)
Worker 0: actual rows=10000.00 loops=1
Batches: 33 Memory Usage: 177kB Disk Usage: 22288kB
```
The rescanned subtree instead contains:
```
-> Gather (actual rows=5005.00 loops=2)
Workers Launched: 1
-> Partial HashAggregate (actual rows=5005.00 loops=2)
Worker 0: actual rows=5005.00 loops=2
Batches: 1 Memory Usage: 65kB
```
The `loops=2` value establishes that the subtree ran twice. Because the
first execution spilled, cumulative worker instrumentation should retain a
batch count greater than one and positive disk usage rather than showing
only the final small execution's values.
The issue was reproduced on PostgreSQL 18.6 on Linux/amd64. This occurs when
a rescannable parallel subtree runs a spilling partial HashAggregate and
later reuses the worker slot for a smaller scan.
| From | Date | Subject | |
|---|---|---|---|
| Next Message | PG Bug reporting form | 2026-11-11 14:04:19 | BUG #19883: Partition pruning errors on SubPlan inside = ANY array |
| Previous Message | Corey Huinker | 2026-11-11 13:46:56 | Re: BUG #19857: Stats restore rejects empty arrays, losing range and array stats |