CommitFest 7308 Review: VACUUM Truncation Threshold Adjustment
report · generated from canonical Markdown
report
postgresql-master
commitfest-7308-review2026-09-18T06:53:32.552241+00:00
Front matter
schema: review-report-v2 target: CommitFest 7308: VACUUM truncation threshold adjustment (should_attempt_truncation) base: 926627bf9021137af66ac3bf21d1595af64ec66b head: 926627bf9021137af66ac3bf21d1595af64ec66b verdict: needs-review blocker_counts: verified: 0 unverified: 0 scope_identity: diff-0ce64c3fe313dc85ee4aadd4397a2196d436ac8fc1bb796f93e3c795bdcbff21 source: postgresql-master slug: commitfest-7308-review commitfest: 7308 tags: [commitfest-7308, pg-review-harness, review-report-v2, needs-review, vacuum, truncation]
CommitFest 7308 Review: VACUUM Truncation Threshold Adjustment
Summary
Target: CommitFest 7308: VACUUM truncation threshold adjustment (should_attempt_truncation) Base/Head (full HEAD): 926627bf9021137af66ac3bf21d1595af64ec66b Scope identity: diff-0ce64c3fe313dc85ee4aadd4397a2196d436ac8fc1bb796f93e3c795bdcbff21 Verdict: needs-review Blocker counts: verified=0, unverified=0 Changed file: src/backend/access/heap/vacuumlazy.c (staged, uncommitted; 3 insertions, 5 deletions) Model: glm-5.2:cloud (explicit user override)
Patch under review
The staged diff lowers REL_TRUNCATE_MINIMUM from 1000 to 2 and REL_TRUNCATE_FRACTION from 16 to 32, and rewrites the should_attempt_truncation predicate from an OR of two independent thresholds (possibly_freeable >= MINIMUM || possibly_freeable >= rel_pages/FRACTION, guarded by possibly_freeable > 0) to a single additive threshold (possibly_freeable >= rel_pages/REL_TRUNCATE_FRACTION + REL_TRUNCATE_MINIMUM). The intent (per the pgsql-hackers thread) is to reduce unnecessary AccessExclusiveLock truncation attempts on relations where the freeable tail is small relative to the relation size.
-#define REL_TRUNCATE_MINIMUM 1000 -#define REL_TRUNCATE_FRACTION 16 +#define REL_TRUNCATE_MINIMUM 2 +#define REL_TRUNCATE_FRACTION 32 ... - if (possibly_freeable > 0 && - (possibly_freeable >= REL_TRUNCATE_MINIMUM || - possibly_freeable >= vacrel->rel_pages / REL_TRUNCATE_FRACTION)) + if (possibly_freeable >= vacrel->rel_pages / REL_TRUNCATE_FRACTION + REL_TRUNCATE_MINIMUM)
Findings
— BLOCKER (correctness) — refuted
Location: src/backend/access/heap/vacuumlazy.c:3152 Mechanism: unsigned-wrap: removal of possibly_freeable > 0 guard on BlockNumber (uint32) allows wrapped subtraction to spuriously trigger truncation when nonempty_pages > rel_pages Failure: spurious should_attempt_truncation true on wrapped possibly_freeable, leading to unnecessary AccessExclusiveLock acquire and rescan in lazy_truncate_heap Verification status: refuted Verification method: static-analysis-plus-compiled-reproducer Verification evidence: Compiled C reproducer (gcc) confirms that on uint32, x > 0 is equivalent to x != 0; a wrapped value (e.g. 4294967291) is non-zero so possibly_freeable > 0 evaluates TRUE for the wrap in BOTH old and new code. The old guard never rejected unsigned wraparound. The zero case (nonempty_pages == rel_pages) is correctly rejected by both old (0 > 0 = false) and new (0 >= 2 = false) code. The downstream lazy_truncate_heap AE-lock recheck (vacuumlazy.c:3228-3239) handles the wrap case identically in both versions. No regression introduced by this patch.
Repair: not-applicable-refuted (all repair sub-objects carry status: not-applicable-refuted).
— MAJOR (correctness) — verified
Location: src/backend/access/heap/hio.c:335 Mechanism: cross-file invariant breakage: REL_TRUNCATE_MINIMUM lowered from 1000 to 2 is no longer 'way larger' than MAX_BUFFERS_TO_EXTEND_BY (64) as documented in hio.c:335-337 comment; the comment is now false and misleading Failure: stale safety comment misleads future maintainers; increased AccessExclusiveLock contention on small busy tables as autovacuum may consider truncating just-extended pages (data loss prevented by lazy_truncate_heap AE-lock recheck at vacuumlazy.c:3228-3239) Verification status: verified Verification method: source-inspection Verification evidence: hio.c:239 defines MAX_BUFFERS_TO_EXTEND_BY=64; hio.c:335-337 comment states 'REL_TRUNCATE_MINIMUM is way larger'; staged diff sets REL_TRUNCATE_MINIMUM=2 < 64. The invariant is inverted. lazy_truncate_heap re-reads RelationGetNumberOfBlocks under AccessExclusiveLock (vacuumlazy.c:3228) and bails if new_rel_pages != orig_rel_pages (3229), preventing data loss. Repair patch compiles clean with gcc 15.2.0 full project CFLAGS.
Repair instruction: Update the hio.c comment to state REL_TRUNCATE_MINIMUM is no longer guaranteed larger than MAX_BUFFERS_TO_EXTEND_BY and document the AE-lock recheck safety mechanism. Update the vacuumlazy.c comment to describe the new additive threshold predicate and its integer-division step behavior. Files: src/backend/access/heap/hio.c, src/backend/access/heap/vacuumlazy.c Postcondition: The hio.c comment accurately describes the relationship between MAX_BUFFERS_TO_EXTEND_BY and REL_TRUNCATE_MINIMUM and documents the safety mechanism relied upon. The vacuumlazy.c comment accurately describes the new predicate.
Code example (exact-postimage, `c):
* Note: REL_TRUNCATE_MINIMUM is no longer guaranteed to be larger than * MAX_BUFFERS_TO_EXTEND_BY. A concurrent autovacuum may therefore * consider truncating pages that were just bulk-extended; that is safe * because lazy_truncate_heap re-reads the relation size under * AccessExclusiveLock and rescans the tail before actually truncating. */
Patch: unified-diff, applies_to_head=926627bf9021137af66ac3bf21d1595af64ec66b, sha256=ae5fc6d32d43a4f80bf9194a5fc0d2a786911cf5ac79c8a940408b4f54c5dff9
Repair validation: status=passed, methods=apply-check, diff-check, compile, static-check Commands: git worktree add --detach <scratch>/verify-wt 926627bf9021137af66ac3bf21d1595af64ec66b; git apply --check repair_combined.diff; git apply repair_combined.diff; git diff --check; gcc -Wall -Wmissing-prototypes ... -I<worktree>/src/include -D_GNU_SOURCE -c -o hio.o src/backend/access/heap/hio.c; gcc -Wall ... -c -o vacuumlazy.o src/backend/access/heap/vacuumlazy.c; grep -c 'way larger' src/backend/access/heap/hio.c Evidence: Both hio.o and vacuumlazy.o compiled cleanly with gcc 15.2.0 and the full PostgreSQL project warning set (-Wall -Werror=vla -Wstrict-prototypes etc). No warnings emitted. Static check confirms stale 'way larger' phrase removed. Patch applies cleanly to frozen HEAD with no whitespace errors.
Test example: vehicle=inspection, files=src/backend/access/heap/hio.c Oracle: grep -c 'way larger' src/backend/access/heap/hio.c returns 0 Baseline: 1 (stale comment present before repair) → Patched: 0 (stale comment removed after repair)
— MINOR (maintainability) — verified
Location: src/backend/access/heap/vacuumlazy.c:165 Mechanism: stale comment: comment describes old OR 'whichever is less' predicate but code now uses additive sum (rel_pages / REL_TRUNCATE_FRACTION + REL_TRUNCATE_MINIMUM) Failure: comment actively misdescribes the should_attempt_truncation predicate logic Verification status: verified Verification method: source-inspection Verification evidence: vacuumlazy.c:165-167 comment says 'REL_TRUNCATE_MINIMUM or (relsize / REL_TRUNCATE_FRACTION) (whichever is less)' describing the old OR predicate; vacuumlazy.c:3153 code is 'possibly_freeable >= vacrel->rel_pages / REL_TRUNCATE_FRACTION + REL_TRUNCATE_MINIMUM' which is an additive sum. Comment is stale.
Repair instruction: Replace the three comment lines describing the OR/whichever-is-less predicate with a description of the new sum predicate and its integer-division behavior. Also update the hio.c comment to reflect the new REL_TRUNCATE_MINIMUM relationship. Files: src/backend/access/heap/hio.c, src/backend/access/heap/vacuumlazy.c Postcondition: The comment accurately describes the should_attempt_truncation predicate.
Code example (exact-postimage, `c):
* To consider truncating the relation, we require at least * (relsize / REL_TRUNCATE_FRACTION) + REL_TRUNCATE_MINIMUM * potentially-freeable pages. Because relsize / REL_TRUNCATE_FRACTION uses * integer division, the threshold collapses to a constant REL_TRUNCATE_MINIMUM * for relations smaller than REL_TRUNCATE_FRACTION pages; relations of * REL_TRUNCATE_FRACTION pages or more add one page per * REL_TRUNCATE_FRACTION pages of size. */
Patch: unified-diff, applies_to_head=926627bf9021137af66ac3bf21d1595af64ec66b, sha256=ae5fc6d32d43a4f80bf9194a5fc0d2a786911cf5ac79c8a940408b4f54c5dff9
Repair validation: status=passed, methods=apply-check, diff-check, compile, static-check Commands: git apply --check repair_combined.diff; git apply repair_combined.diff; gcc -Wall ... -c -o vacuumlazy.o src/backend/access/heap/vacuumlazy.c; grep -c 'whichever' src/backend/access/heap/vacuumlazy.c Evidence: vacuumlazy.o compiled cleanly with full project CFLAGS. Comment-only change. Static check confirms 'whichever' removed. No warnings.
Test example: vehicle=inspection, files=src/backend/access/heap/vacuumlazy.c Oracle: grep -c 'whichever' src/backend/access/heap/vacuumlazy.c returns 0 Baseline: 1 (stale comment present) → Patched: 0 (stale comment removed)
— MINOR (maintainability) — verified
Location: src/backend/access/heap/vacuumlazy.c:3153 Mechanism: undocumented integer-division step function: rel_pages / REL_TRUNCATE_FRACTION (integer division of uint32 by 32) collapses to 0 for rel_pages < 32, making the threshold a constant 2 for all relations under 32 pages Failure: threshold is a step function (constant 2 for <32 pages, +1 per 32 pages above) that is not documented; the old 1000-page minimum masked this behavior Verification status: verified Verification method: compiled-reproducer Verification evidence: Compiled C reproducer enumerating rel_pages 0..40 confirms rel_pages/32 is 0 for rel_pages < 32 and steps up by 1 every 32 pages. Threshold table: rel_pages=0..31 -> threshold=2; rel_pages=32..63 -> threshold=3; rel_pages=64..95 -> threshold=4. The F3 comment repair now documents this explicitly.
Repair instruction: Document the integer-division step function in the vacuumlazy.c comment (same edit as F3). Also update the hio.c comment. Files: src/backend/access/heap/hio.c, src/backend/access/heap/vacuumlazy.c Postcondition: The comment documents that the threshold is constant REL_TRUNCATE_MINIMUM for relations smaller than REL_TRUNCATE_FRACTION pages.
Code example (exact-postimage, `c):
* integer division, the threshold collapses to a constant REL_TRUNCATE_MINIMUM * for relations smaller than REL_TRUNCATE_FRACTION pages; relations of * REL_TRUNCATE_FRACTION pages or more add one page per * REL_TRUNCATE_FRACTION pages of size. */
Patch: unified-diff, applies_to_head=926627bf9021137af66ac3bf21d1595af64ec66b, sha256=ae5fc6d32d43a4f80bf9194a5fc0d2a786911cf5ac79c8a940408b4f54c5dff9
Repair validation: status=passed, methods=apply-check, diff-check, compile, static-check Commands: git apply --check repair_combined.diff; git apply repair_combined.diff; gcc -Wall ... -c -o vacuumlazy.o src/backend/access/heap/vacuumlazy.c; grep -c 'collapses to a constant REL_TRUNCATE_MINIMUM' src/backend/access/heap/vacuumlazy.c Evidence: Comment-only change documents the step function; compiles cleanly. Static check confirms documentation added.
Test example: vehicle=inspection, files=src/backend/access/heap/vacuumlazy.c Oracle: grep -c 'collapses to a constant REL_TRUNCATE_MINIMUM' returns >= 1 Baseline: 0 (not documented) → Patched: 1 (documented in comment)
— MINOR (tests) — verified
Location: src/test/regress/sql/vacuum.sql:192 Mechanism: no regression test added for the threshold change; existing vacuum.sql tests vacuum_truncate reloption but not the should_attempt_truncation threshold boundary Failure: threshold logic is otherwise untested; the arithmetic that regresses silently has no dedicated test Verification status: verified Verification method: test-suite-inspection Verification evidence: src/test/regress/sql/vacuum.sql lines 192-210 test vacuum_truncate reloption (TRUNCATE FALSE) but no test asserts that a relation with N trailing empty pages is/isnt truncated based on the threshold. No test covers the new rel_pages/32+2 formula.
Repair instruction: Add a regress test that creates a temp table with autovacuum_enabled=false, inserts 5000 rows, deletes id > 1000, vacuums, and asserts the relation shrank. Add a second case: insert 200 rows, delete all, vacuum, and assert the relation still has pages (behavior change). Add test: vacuum_truncate_threshold to parallel_schedule. Files: src/test/regress/sql/vacuum_truncate_threshold.sql, src/test/regress/expected/vacuum_truncate_threshold.out, src/test/regress/parallel_schedule Postcondition: pg_regress vacuum_truncate_threshold passes with expected output matching.
Code example (exact-postimage, `sql):
INSERT 0 5000
Patch: unified-diff, applies_to_head=926627bf9021137af66ac3bf21d1595af64ec66b, sha256=22af8e4139ea16ca4a30083c3399019d9e6c69b3fff9d55845c2e138766b9180
Repair validation: status=passed, methods=apply-check, diff-check, targeted-test Commands: git apply --check repair_f5_test.diff; git apply repair_f5_test.diff; psql -p 54335 -d postgres -f vacuum_truncate_threshold.sql Evidence: Test applies cleanly to frozen HEAD. Executed against patched PostgreSQL 18devel cluster: 5000-row case truncates (before=23 pages, after=5 pages, truncated=t). 200-row case confirms behavior change (relation retains pages). Output deterministic across two consecutive runs.
Test example: vehicle=regress, files=src/test/regress/sql/vacuum_truncate_threshold.sql, src/test/regress/expected/vacuum_truncate_threshold.out Oracle: truncated=t (23->5 pages); not_fully_truncated=t (200-row table retains pages after VACUUM due to threshold >= 2) Baseline: n/a (new test) → Patched: truncated=t, not_fully_truncated=t
— MAJOR (tests) — verified
Location: src/test/regress/expected/vacuum.out:242 Mechanism: existing regression test breakage: vacuum.sql lines 199 and 209 assert pg_relation_size('vac_truncate_test') = 0 is true after VACUUM, but the new threshold (rel_pages/32 + 2 >= 2) prevents truncation of tiny (1-page) relations that the old threshold (min(1000, rel_pages/16) = 0 for 1 page) allowed Failure: make check-tests TESTS=vacuum fails with 2 assertion mismatches: expected t got f at vacuum.out:242 and vacuum.out:269 Verification status: verified Verification method: targeted-test Verification evidence: Ran make -C src/test/regress check-tests TESTS=vacuum against patched code: 1 of 1 tests failed. regression.diffs shows 2 hunks where 't' expected but 'f' produced for pg_relation_size('vac_truncate_test') = 0. After applying the expected-output repair, the vacuum test passes (ok 1 - vacuum). The behavior change is intentional per the thread but the existing test was not updated.
Repair instruction: Update the two pg_relation_size('vac_truncate_test') = 0 expected results from ' t' to ' f' to reflect that tiny relations no longer truncate to 0 pages under the new threshold. Files: src/test/regress/expected/vacuum.out Postcondition: make check-tests TESTS=vacuum passes with the patched code.
Code example (exact-postimage, `diff):
f (1 row) DROP TABLE vac_truncate_test;
Patch: unified-diff, applies_to_head=926627bf9021137af66ac3bf21d1595af64ec66b, sha256=f9f6d0d6cb7aad47e013be16fd716ed2b31b749197adfbf982eaa43f81398e4a
Repair validation: status=passed, methods=apply-check, diff-check, targeted-test Commands: git apply --check repair_f6_vacuum_expected.diff; git apply repair_f6_vacuum_expected.diff; make -C src/test/regress check-tests TESTS=vacuum Evidence: Applied the expected-output patch on top of the staged diff; make check-tests TESTS=vacuum passes: 'ok 1 - vacuum 416 ms' / 'All 1 tests passed.' The 2 assertions now correctly expect f.
Test example: vehicle=regress, files=src/test/regress/expected/vacuum.out Oracle: pg_relation_size('vac_truncate_test') = 0 returns f (relation retains >= 1 page) Baseline: t (old code truncated tiny relations to 0) → Patched: f (new code does not truncate tiny relations to 0)
Verification Summary
| Finding | Severity | Class | Verification | Repair Validation |
|---|---|---|---|---|
| F1 | BLOCKER | correctness | refuted | n/a (refuted) |
| F2 | MAJOR | correctness | verified | passed |
| F3 | MINOR | maintainability | verified | passed |
| F4 | MINOR | maintainability | verified | passed |
| F5 | MINOR | tests | verified | passed |
| F6 | MAJOR | tests | verified | passed |
- F1 (BLOCKER): REFUTED. The claim that removing
possibly_freeable > 0introduces an unsigned-wrap regression is false. Onuint32,x > 0is equivalent tox != 0; a wrapped value (e.g.4294967291) is non-zero, so the old guard also evaluatedTRUEfor wrapped subtraction. Both old and new code rely on the downstreamlazy_truncate_heapAE-lock recheck (vacuumlazy.c:3228-3239) to handle the wrap case. The zero case (nonempty_pages == rel_pages) is correctly rejected by both old (0 > 0) and new (0 >= 2) code. No regression is introduced. - F2 (MAJOR): VERIFIED. The
hio.c:335-337comment documents thatREL_TRUNCATE_MINIMUMis "way larger" thanMAX_BUFFERS_TO_EXTEND_BY(64). Lowering to 2 inverts this invariant. The comment is now false. Data loss is prevented by the AE-lock recheck, but the comment must be updated. - F3 (MINOR): VERIFIED. The
vacuumlazy.c:165-167comment describes the old OR/"whichever is less" predicate; the code now uses an additive sum. - F4 (MINOR): VERIFIED. Integer division makes the threshold a step function (constant 2 for <32 pages, +1 per 32 pages above) that is undocumented.
- F5 (MINOR): VERIFIED. No regression test covers the threshold change.
- F6 (MAJOR): VERIFIED. The patch breaks 2 existing
vacuum.sqlassertions (pg_relation_size('vac_truncate_test') = 0expectedt, nowf) because tiny relations no longer truncate to 0 pages under the new threshold. Discovered during coverage execution.
Repair Validation Summary
All repair patches were validated in a disposable git worktree at frozen HEAD 926627bf9021137af66ac3bf21d1595af64ec66b (never touching the read-only review worktree).
- F2/F3/F4 combined repair (
repair_combined.diff, sha256ae5fc6d32d43a4f80bf9194a5fc0d2a786911cf5ac79c8a940408b4f54c5dff9): comment-only changes tohio.candvacuumlazy.cthat update the stale invariant comment, describe the new additive predicate, and document the integer-division step function. Validated withgit apply --check,git apply,git diff --check, andgcccompilation of bothhio.oandvacuumlazy.owith the full PostgreSQL project CFLAGS — no warnings. - F5 test repair (
repair_f5_test.diff, sha25622af8e4139ea16ca4a30083c3399019d9e6c69b3fff9d55845c2e138766b9180): newvacuum_truncate_threshold.sql+.outregress test +parallel_scheduleentry. Validated withgit apply --checkand live execution against a patched PostgreSQL 18devel cluster: 5000-row case truncates (23→5 pages,truncated=t); 200-row case confirms behavior change (relation retains pages). Output deterministic across two runs. - F6 expected-output repair (
repair_f6_vacuum_expected.diff, sha256f9f6d0d6cb7aad47e013be16fd716ed2b31b749197adfbf982eaa43f81398e4a): updates 2 assertions invacuum.outfromttof. Validated withmake check-tests TESTS=vacuumpassing (ok 1 - vacuum) after applying both the staged diff and this expected-output patch.
Changed-Code Coverage
Status: measured Counter epoch: fresh Head: 926627bf9021137af66ac3bf21d1595af64ec66b Execution lane: host-disposable (passed)
Execution evidence
- Lane:
host-disposable, status: passed
Disposable git worktree at frozen HEAD 926627bf902 with staged diff applied; backend compiled with gcc 15.2.0 -O0 -g --coverage -fprofile-arcs -ftest-coverage (vacuumlazy.c instrumented); PostgreSQL 18devel cluster started on port 54335; F5 threshold test executed (5000-row insert, delete 4000, VACUUM; 200-row insert, delete all, VACUUM); gcov -b -o src/backend/access/heap vacuumlazy.c produced coverage data showing should_attempt_truncation called 2 times, line 3153 both branches taken (50%/50%), line 3154 return true covered (1 hit), line 3156 return false covered (1 hit). Existing vacuum.sql regress test also run, revealing 2 assertion failures (F6).
Test commands
psql -p 54335 -d postgres -f vacuum_truncate_threshold.sqlmake -C src/test/regress check-tests TESTS=vacuum
Metrics
| Metric | Value |
|---|---|
| changed_executable_lines | 1 |
| covered_changed_lines | 1 |
| compiler_uncovered_changed_lines | 0 |
| uncovered_changed_lines | 0 |
| structural_uncovered_changed_lines | 0 |
| changed_lines_without_coverage_coordinates | 2 |
| changed_branches | 2 |
| covered_changed_branches | 2 |
| integrity_errors | [] |
Touched functions
should_attempt_truncation(src/backend/access/heap/vacuumlazy.c:3145-3157): entered=True, lines 4/4, branches 2/2
Excluded changed coordinates
- compiler-no-coverage-coordinate (
src/backend/access/heap/vacuumlazy.c, count=2): Lines 169 and 170 are #define macro definitions (REL_TRUNCATE_MINIMUM, REL_TRUNCATE_FRACTION) which are preprocessor constants, not executable code; gcc/gcov emits no coverage coordinates for them.
Uncovered regions / branches
None. The single changed executable line (3153) and both of its branches (true→3154, false→3156) are fully covered by the F5 threshold test execution. The only uncovered line in should_attempt_truncation is line 3150 (return false on !do_rel_truncate || VacuumFailsafeActive), which is unchanged context, not a changed line.
Coverage finding (F6)
During coverage execution, running the existing vacuum.sql regression test against the patched code revealed 2 assertion failures (F6): pg_relation_size('vac_truncate_test') = 0 returns f instead of t at two sites. This is a behavior change — tiny relations (1 page) no longer truncate to 0 pages because the new threshold rel_pages/32 + 2 = 2 exceeds possibly_freeable = 1. The old threshold min(1000, rel_pages/16) = 0 allowed truncation. This is intentional per the thread but the existing test expected output was not updated.
pgsql-hackers Thread Ledger
Status: reconciled Ledger report slug: commitfest-7308-review-thread-ledger Reconstruction methods: amauta-mail-search-BM25, thread-id-grouping, message-content-inspection Summary: Located the pgsql-hackers thread 'Optimize truncation logic to reduce AccessExclusive lock impact' (Thread-ID 607107) via BM25 search on REL_TRUNCATE_MINIMUM and should_attempt_truncation. The thread has 4 messages: the original patch proposal by Stepan Neretin (slpmcf@gmail.com, 2025-03-18), a review comment by David Rowley (dgrowleyml@gmail.com, 2025-03-19) noting the comment becomes stale, and two replies by Yura Sokolov (y.sokolov@postgrespro.ru, 2025-03-19 and 2025-03-21) discussing patch authorship and large-relation behavior. The thread does NOT raise the unsigned-wrap guard (F1, refuted), the hio.c invariant breakage (F2), or the existing-test breakage (F6) — these are new findings from this review. David Rowley's message hints at the stale comment (F3) by quoting the comment block.
Thread 607107: Optimize truncation logic to reduce AccessExclusive lock impact
Participants: slpmcf@gmail.com, dgrowleyml@gmail.com, y.sokolov@postgrespro.ru
| Date | Sender | Kind | Summary |
|---|---|---|---|
| 2025-03-18T06:03:38+00:00 | slpmcf@gmail.com | patch | Stepan Neretin proposes modifying should_attempt_truncation to require at least 3% of relation size + 2 pages freeable b... |
| 2025-03-19T02:09:07+00:00 | dgrowleyml@gmail.com | review | David Rowley traces the code through DropRelationBuffers and comments that the patch seems to answer Yes to the question... |
| 2025-03-19T16:05:13+00:00 | y.sokolov@postgrespro.ru | author-reply | Yura Sokolov (patch author Yuriy Sokolov) agrees the patch works for smaller relations but notes it does not fix large r... |
| 2025-03-21T11:50:22+00:00 | y.sokolov@postgrespro.ru | author-reply | Yura Sokolov clarifies that for large tables truncated on a small number of pages, knowing old and new size allows preci... |
Finding links
| Finding | Disposition | Thread references | Rationale |
|---|---|---|---|
| F1 | refuted | (none) | The unsigned-wrap guard removal was not raised in the thread. The finding was refuted by compiled-re... |
| F2 | new | (none) | The hio.c invariant breakage (REL_TRUNCATE_MINIMUM no longer way larger than MAX_BUFFERS_TO_EXTEND_B... |
| F3 | already-raised | CAApHDvrfngqCpQ09LKdr-BnJEVHW0=wAhxfRBnw=HJ2645cJPg@mail.gmail.com | David Rowley's review message quotes the stale comment block, implicitly noting it describes the old... |
| F4 | new | (none) | The integer-division step-function collapse for rel_pages < 32 is not discussed in the thread.... |
| F5 | new | (none) | No test coverage for the threshold change is discussed in the thread.... |
| F6 | new | (none) | The breakage of existing vacuum.sql regression test assertions is not discussed in the thread. The a... |
Deterministic Verdict
Verdict: needs-review
Verdict logic (post-verification):
- F1 (BLOCKER): refuted → dropped from counts; no surviving BLOCKER.
- F2 (MAJOR, correctness): verified → surviving correctness MAJOR.
- F6 (MAJOR, tests): verified → surviving tests MAJOR.
- F3, F4, F5 (MINOR): verified → minor findings.
- Any surviving correctness/security MAJOR →
needs-review.
Blocker split: verified=0, unverified=0.
The patch requires updates before it is committer-ready: (1) update the stale hio.c comment (F2), (2) update the stale vacuumlazy.c comment and document the integer-division step function (F3/F4), (3) add a regression test for the threshold change (F5), and (4) update the existing vacuum.sql expected output to reflect the behavior change (F6). The F1 BLOCKER (unsigned-wrap guard removal) was refuted and requires no change.
Aggregated Test Plan
- New regress test
vacuum_truncate_threshold.sql(F5): exercises truncation on a 23-page table (delete 4000 of 5000 rows, VACUUM, assert truncation) and on a small table (200 rows, delete all, VACUUM, assert relation retains pages — behavior change). - Updated expected output
vacuum.out(F6): 2 assertions updated fromttofforpg_relation_size('vac_truncate_test') = 0. - Static inspection oracles:
grep -c 'way larger' hio.c→ 0;grep -c 'whichever' vacuumlazy.c→ 0;grep -c 'collapses to a constant' vacuumlazy.c→ ≥1.
Residual Risk
- The patch changes autovacuum behavior for all deployments: small relations with ≥2 trailing empty pages will now be considered for truncation more often (increased AE-lock contention on small busy tables). This is the intended tradeoff per the thread.
- The
hio.cinvariant (REL_TRUNCATE_MINIMUM ≫ MAX_BUFFERS_TO_EXTEND_BY) is broken; safety relies entirely on thelazy_truncate_heapAE-lock recheck. A future refactor that weakens that recheck could expose a data-loss path. The F2 comment repair documents this reliance. - No catversion bump required (no catalog/on-disk format change).
Provenance
| Seat | Model | Handoff report slug | markdown_sha256 |
|---|---|---|---|
| pg-patch-reviewer | glm-5.2:cloud | commitfest-7308-review-handoff-patch-reviewer | 9b177fca8afa7bd47a113ce4c69ce7277c7ef1621d191b7d04cffc84eb007861 |
| pg-security-reviewer | glm-5.2:cloud | commitfest-7308-review-handoff-security-reviewer | b735e4cf4719452afd6ff4f5b326e5d314574984ab77dc0d8037e35906386870 |
| pg-finding-verifier | glm-5.2:cloud | commitfest-7308-review-handoff-verifier | ea76bb16ca6f48a7ba099e0896a074b02211a94d0748414c4d1983c05b3d9717 |
| pg-change-coverage-reviewer (host-disposable) | glm-5.2:cloud | (inline, coverage executed by outer harness) | — |
Router policy: version=1, hash=997c035512fa1b35aa17149ea386a861af531ec05839ce06810f38268a3caebe. User override: glm-5.2:cloud.
Artifact validation
The structured pg-review-artifact-v2 JSON artifact was validated with the supplied validator (the supplied run-scoped validator) and passed: ok: pg-review-artifact-v2 is structurally complete. Artifact path: the supplied run-scoped artifact path (review-artifact-v2.json).