-- test/sql/vacuum_pending_dead.sql -- -- A dead TID still in the pending list must not outlive its heap tuple. -- -- ambulkdelete stripped dead TIDs only from the posting trees. A row -- inserted and deleted between two VACUUMs is still in the pending list, -- so it survived the strip, was merged into a tree by amvacuumcleanup -- right after, and stayed there -- while the heap freed the line pointer -- and truncated the block. The next index scan fetched a TID past the -- heap's end: "could not read blocks 1..1 ... read only 0 of 8192 bytes" -- (field report 2026-09-29). ambulkdelete now merges the pending list -- first, as ginbulkdelete does. CREATE EXTENSION IF NOT EXISTS pg_tre; psql:test/sql/vacuum_pending_dead.sql:14: NOTICE: extension "pg_tre" already exists, skipping CREATE EXTENSION DROP TABLE IF EXISTS vpd; psql:test/sql/vacuum_pending_dead.sql:16: NOTICE: table "vpd" does not exist, skipping DROP TABLE CREATE TABLE vpd (id serial PRIMARY KEY, body text) WITH (autovacuum_enabled = off); CREATE TABLE SET client_min_messages = warning; SET CREATE INDEX vpd_tre ON vpd USING tre (body); CREATE INDEX RESET client_min_messages; RESET -- Fills several heap blocks; every entry goes to the pending list. INSERT INTO vpd (body) SELECT 'ghost_' || g || repeat(' pad', 40) FROM generate_series(1, 5000) g; INSERT 0 5000 SELECT pg_relation_size('vpd') / 8192 > 1 AS several_heap_blocks; several_heap_blocks --------------------- t (1 row) DELETE FROM vpd; DELETE 5000 VACUUM vpd; VACUUM SELECT pg_relation_size('vpd') AS heap_bytes_after_truncate; heap_bytes_after_truncate --------------------------- 0 (1 row) INSERT INTO vpd (body) VALUES ('alive'); INSERT 0 1 SET enable_seqscan = off; SET SELECT count(*) AS ghosts_via_index FROM vpd WHERE body ~ 'ghost_4'; ghosts_via_index ------------------ 0 (1 row) SELECT count(*) AS alive_via_index FROM vpd WHERE body ~ 'alive'; alive_via_index ----------------- 1 (1 row) RESET enable_seqscan; RESET -- Same through flush_to_run, whose merges land in catalog runs rather -- than the base tree; ambulkdelete must walk those too. TRUNCATE vpd; psql:test/sql/vacuum_pending_dead.sql:41: NOTICE: pg_tre: collected 0 trigram entries from 0 heap tuples psql:test/sql/vacuum_pending_dead.sql:41: NOTICE: pg_tre: built 0 posting trees psql:test/sql/vacuum_pending_dead.sql:41: NOTICE: pg_tre: build complete, indexed 0 heap tuples into 0 trigrams TRUNCATE TABLE SET pg_tre.flush_to_run = on; SET INSERT INTO vpd (body) SELECT 'wraith_' || g || repeat(' pad', 40) FROM generate_series(1, 5000) g; INSERT 0 5000 VACUUM vpd; -- flushes the pending list into a run VACUUM DELETE FROM vpd; DELETE 5000 VACUUM vpd; VACUUM RESET pg_tre.flush_to_run; RESET INSERT INTO vpd (body) VALUES ('alive'); INSERT 0 1 SET enable_seqscan = off; SET SELECT count(*) AS wraiths_via_index FROM vpd WHERE body ~ 'wraith_4'; wraiths_via_index ------------------- 0 (1 row) RESET enable_seqscan; RESET DROP TABLE vpd; DROP TABLE