From 80ecba3bcef1937439d06ac484ecb50fbb328294 Mon Sep 17 00:00:00 2001 From: Peter Geoghegan Date: Wed, 29 Jul 2026 10:29:10 -0400 Subject: [PATCH v1] Fix autovacuum-induced flakiness in backwards scan test. Two of the permutations in backwards-scan-concurrent-splits rely on their VACUUM step deleting the leaf pages that the waiting backwards scan will have to recover from. VACUUM can only do that when it's able to remove the index tuples whose heap tuples the concurrent session just deleted. An autovacuum worker holding a snapshot holds back the removable cutoff, which leaves the pages non-empty, and so undeleted, causing the test to fail spuriously. To fix, wait for the removable cutoff to advance past the deletions before the scan acquires its snapshot. This is much like commit 1c64d2fc, which dealt with the same hazard in nbtree_half_dead_pages by adding the wait_prunable() helper that we reuse here. Oversight in commit e395fbd3. Author: Peter Geoghegan Reported-by: Alexander Lakhin Discussion: https://postgr.es/m/b61d9944-d7a3-45f3-b69a-f18c8bfbbbd0@gmail.com --- .../backwards-scan-concurrent-splits.out | 6 +++-- .../backwards-scan-concurrent-splits.spec | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/test/modules/nbtree/expected/backwards-scan-concurrent-splits.out b/src/test/modules/nbtree/expected/backwards-scan-concurrent-splits.out index 906c10d10..804e690dc 100644 --- a/src/test/modules/nbtree/expected/backwards-scan-concurrent-splits.out +++ b/src/test/modules/nbtree/expected/backwards-scan-concurrent-splits.out @@ -162,7 +162,7 @@ injection_points_detach (1 row) -starting permutation: b_attach d_delete_left b_scan vacuum_tbl i_detach b_detach +starting permutation: b_attach d_delete_left wait_prunable b_scan vacuum_tbl i_detach b_detach step b_attach: SELECT injection_points_attach('nbtree-walk-left', 'wait'); SELECT injection_points_attach('nbtree-walk-left-step-right', 'notice'); @@ -190,6 +190,7 @@ injection_points_attach (1 row) step d_delete_left: DELETE FROM backwards_scan_tbl WHERE col < 601; +step wait_prunable: CALL wait_prunable(); step b_scan: SELECT col FROM backwards_scan_tbl WHERE col % 100 = 1 AND pg_backend_pid() <> 0 ORDER BY col DESC; @@ -236,7 +237,7 @@ injection_points_detach (1 row) -starting permutation: b_attach_nosr i_grow d_delete_mid b_scan_999 vacuum_tbl i_detach b_detach_nosr +starting permutation: b_attach_nosr i_grow d_delete_mid wait_prunable b_scan_999 vacuum_tbl i_detach b_detach_nosr step b_attach_nosr: SELECT injection_points_attach('nbtree-walk-left', 'wait'); SELECT injection_points_attach('nbtree-walk-left-restart', 'notice'); @@ -259,6 +260,7 @@ injection_points_attach step i_grow: INSERT INTO backwards_scan_tbl SELECT i FROM generate_series(701, 2200) i; step d_delete_mid: DELETE FROM backwards_scan_tbl WHERE col BETWEEN 367 AND 2100; +step wait_prunable: CALL wait_prunable(); step b_scan_999: SELECT col FROM backwards_scan_tbl WHERE col <= 999 AND col % 100 = 1 AND pg_backend_pid() <> 0 ORDER BY col DESC; diff --git a/src/test/modules/nbtree/specs/backwards-scan-concurrent-splits.spec b/src/test/modules/nbtree/specs/backwards-scan-concurrent-splits.spec index 62c0cf25a..7e35dc883 100644 --- a/src/test/modules/nbtree/specs/backwards-scan-concurrent-splits.spec +++ b/src/test/modules/nbtree/specs/backwards-scan-concurrent-splits.spec @@ -21,6 +21,26 @@ setup CREATE TABLE backwards_scan_tbl(col int4) WITH (autovacuum_enabled = off); CREATE INDEX ON backwards_scan_tbl(col) WITH (deduplicate_items = off); INSERT INTO backwards_scan_tbl SELECT i FROM generate_series(0, 700) i; + -- Wait until every dead tuple in the table has become removable by VACUUM. + -- Autovacuum can hold a snapshot open in any database at any time, which + -- holds back the removable cutoff. + CREATE PROCEDURE wait_prunable() LANGUAGE plpgsql AS $$ + DECLARE + barrier xid8; + cutoff xid8; + BEGIN + barrier := pg_current_xact_id(); + -- Pass a shared catalog rather than the table we'll prune, to prevent + -- the cutoff from moving backwards. See comments at removable_cutoff() + LOOP + ROLLBACK; -- release MyProc->xmin, which could be the oldest + cutoff := removable_cutoff('pg_database'); + EXIT WHEN cutoff >= barrier; + RAISE LOG 'removable cutoff %; waiting for %', cutoff, barrier; + PERFORM pg_sleep(.1); + END LOOP; + END + $$; } setup { @@ -30,6 +50,7 @@ teardown { DROP EXTENSION injection_points; DROP TABLE backwards_scan_tbl; + DROP PROCEDURE wait_prunable; } session scan_session @@ -76,6 +97,7 @@ step i_insert_dups { INSERT INTO backwards_scan_tbl SELECT 100 FROM generate_ser step i_grow { INSERT INTO backwards_scan_tbl SELECT i FROM generate_series(701, 2200) i; } step d_delete_left { DELETE FROM backwards_scan_tbl WHERE col < 601; } step d_delete_mid { DELETE FROM backwards_scan_tbl WHERE col BETWEEN 367 AND 2100; } +step wait_prunable { CALL wait_prunable(); } step vacuum_tbl { VACUUM backwards_scan_tbl; } step i_detach { SELECT injection_points_detach('nbtree-walk-left'); @@ -104,6 +126,7 @@ permutation b_attach # the scan has no page to the left to move to, ending the scan. permutation b_attach d_delete_left + wait_prunable b_scan vacuum_tbl i_detach @@ -117,6 +140,7 @@ permutation b_attach permutation b_attach_nosr i_grow d_delete_mid + wait_prunable b_scan_999 vacuum_tbl i_detach -- 2.53.0