Incremental Analysis: Root Cause Confirmed, Fix Strategy Agreed
Summary of Progress
The thread has moved from initial bug report to full root cause identification, a proposed patch, expert review, and consensus on a fix strategy — all within 48 hours.
Root Cause Confirmed (Two Interacting Commits)
Ewan (kdbase.hack) identified the precise two-commit interaction causing the bug:
- 4f7ecca84dd — Added unconditional (extending)
visibilitymap_pin()in the on-access prune path, meaning any scan that triggers pruning may now extend the VM fork. - 378a216187a — Made INSERT set
pd_prune_xid, so on-access pruning now fires on insert-mostly catalogs likepg_database.
The race condition: the autovacuum launcher scans pg_database via get_database_list() with a catalog scan. On a full, prunable page, heap_page_prune_opt() calls visibilitymap_pin() which extends the VM fork. The launcher backend type is not permitted to do IOOP_EXTEND in pgstat_tracks_io_op(), triggering the assertion. The window is narrow because any regular backend or autovacuum worker scanning pg_database first would create the fork harmlessly.
Proposed Patch (Ewan's approach)
Ewan submitted a patch that conditionally avoids extending the VM fork: for non-read-only scans, it uses visibilitymap_get_status() (which only pins existing pages without extending) instead of visibilitymap_pin() (which extends). The rationale: if the VM doesn't cover the page yet, there's no corruption to detect, and the fork creation can be deferred to the next VACUUM.
Melanie's Authoritative Response and Fix Strategy
Melanie Plageman (author of the affected code) provided a detailed response:
-
For PG19 (backport fix): Simply relax
pgstat_tracks_io_op()to allowIOOP_EXTENDforB_AUTOVAC_LAUNCHER. The specific change removes the autovacuum launcher from the list of backend types that are blocked from extending:- if ((bktype == B_AUTOVAC_LAUNCHER || bktype == B_BG_WRITER || - bktype == B_CHECKPOINTER) && io_op == IOOP_EXTEND) + if ((bktype == B_BG_WRITER || bktype == B_CHECKPOINTER) && + io_op == IOOP_EXTEND) -
For PG20 (proper fix): Add a flags argument to
table_beginscan_catalog()to properly communicate scan intent, combined with thepgstat_tracks_io_op()change. -
Melanie's hesitation on Ewan's patch: She acknowledged it's not wrong but expressed discomfort that it breaks the invariant that the VM page is always pinned and passed to
heap_page_prune_and_freeze(). The logic relies on implicit knowledge thatvisibilitymap_pin()extends whilevisibilitymap_get_status()doesn't — making the code harder to reason about. -
Design context:
SO_HINT_REL_READ_ONLYis only a performance hint, not a guarantee. Melanie noted they briefly discussed excluding catalog scans in the original thread but didn't pursue it.
Consensus Reached
Ewan agreed to drop his patch in favor of Melanie's simpler approach: relax the assertion for PG19, do the proper architectural fix in PG20.