Add regression test for FSM vacuum of bulk-extended relations#68
Draft
NikolayS wants to merge 1 commit into
Draft
Add regression test for FSM vacuum of bulk-extended relations#68NikolayS wants to merge 1 commit into
NikolayS wants to merge 1 commit into
Conversation
11 tasks
Commit a2fd8d6 ("Include last block in FSM vacuum of bulk extended relation") fixed an off-by-one in RelationAddBlocks(): the exclusive end block passed to FreeSpaceMapVacuumRange() was the number of the last added block instead of one past it. The bug only manifested when the last bulk-extended block was the first slot of a new FSM leaf page (heap block SlotsPerFSMPage, 2*SlotsPerFSMPage, ...), in which case that block's free space was recorded in the leaf FSM page but never propagated to the upper FSM levels, leaving it invisible to FSM searches until the next full FSM vacuum. The fix shipped without test coverage. Add a TAP test to contrib/pg_freespacemap that closes that gap. It bulk-extends a relation with COPY (which uses a BulkInsertState and thus takes the bulk-extension path) so that the final extension's last block lands exactly on an FSM leaf-page boundary. The relation is sized from the block size and the measured tuples-per-page, and the extension grid is shifted with a committed prefill so the boundary is reached deterministically. Detection uses the invariant a2fd8d6 restores rather than any hard-coded FSM page layout: because the bulk extension has already propagated the new free space up the tree, an immediately following VACUUM must not raise any FSM node value (it may only lower stale entries for partially filled pages). With the bug present the boundary block's free space is missing from the upper levels, so the redundant VACUUM raises the parent node from zero and the test fails. pg_freespace() reads the leaf value directly and is unaffected by the bug, so it cannot be used to detect it. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01PtxA6B4d47ietk5dGsseuP
NikolayS
force-pushed
the
claude/testplan-fsm-vm-boundary
branch
from
July 18, 2026 15:31
911caab to
ce6ae56
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a TAP regression test (
contrib/pg_freespacemap/t/001_bulk_extend_fsm.pl, 151 lines incl. comments) covering the off-by-one that commit a2fd8d6 ("Include last block in FSM vacuum of bulk extended relation") fixed. That fix shipped with no test.Why
RelationAddBlocks()bulk-extends a relation, records the not-immediately-used new blocks in the FSM, and callsFreeSpaceMapVacuumRange(rel, first, last)to propagate that free space up the FSM tree. The end argument is exclusive, but the code passed the last added block number instead oflast + 1. The bug only bites when that last block is the first slot of a new FSM leaf page (heap blockSlotsPerFSMPage= 4069 for the default 8 kBBLCKSZ,2*SlotsPerFSMPage, …): its free space lands in the leaf FSM page but never reaches the upper levels, so it stays invisible to FSM searches (GetPageWithFreeSpace) until the next full FSM vacuum.pg_freespace()reads the leaf value directly, so it is unaffected by the bug and cannot detect it.How the test works
block_sizeand the measured tuples-per-page.SlotsPerFSMPageis used only to size the relation so a bulk extension lands on the boundary — the detection needs no knowledge of the FSM page node layout.COPY(which uses aBulkInsertState, taking the bulk-extension path). The extension grid is 64-block-periodic once saturated; the test learns the grid offset from a small probe COPY and shifts it with a committed prefill so the final extension's last block lands exactly on the FSM leaf-page boundary.VACUUMmust not raise any FSM node value (it may only lower stale entries for partially-filled pages). The test snapshots every non-zero FSM node (pageinspect.fsm_page_contents), runsVACUUM, and asserts no node value increased. With the bug, the boundary block's free space is missing from the upper levels, so the redundant VACUUM raises the parent node from 0 and the test fails.Changes from the first revision (reviewer feedback)
contrib/pg_visibilitycoverage (test_vac_unmodified_heap,copyfreezealready exercisepg_visibility_map_summary/pg_check_visible/pg_check_frozenafter VACUUM; a real VM page boundary is 32672 blocks ≈ 255 MB, infeasible to test).pg_visibilityis no longer anEXTRA_INSTALLdependency.nblocks == boundary+1), the leaf-has-free-space check (isolates the bug to propagation, since the leaf is unaffected), and the core propagation invariant.Provenance / test evidence
d374280e1837adc1f8218bb0d69ffbae5d808fc6(master, v19 devel), which already contains a2fd8d6.meson setup build --buildtype=debug -Dcassert=true -Dtap_tests=enabledFix present — all pass:
Gold-standard revert verification. Procedure: in the same worktree, revert a2fd8d6's one-line change (
FreeSpaceMapVacuumRange(relation, first_fsm_block, last_block + 1)→... last_block)), rebuildsrc/backend/postgres, refresh it into the mesontmp_install, and re-run the test. Result — the core assertion fails:The single raised node is the parent-page slot for the boundary block's FSM leaf page (
block 1, node 4096: 0 → 255). Restored the fix afterward;hio.cis unmodified in this branch.Closes #59
🤖 Generated with Claude Code
https://claude.ai/code/session_01PtxA6B4d47ietk5dGsseuP