From 218bcd4dffe014647495a9bba11d8beaeb1465cd Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Tue, 2 Dec 2025 15:57:34 -0500 Subject: [PATCH v28 05/15] Move VM assert into prune/freeze code This is a step toward setting the VM in the same WAL record as pruning and freezing. It moves the check of the heap page into prune/freeze code before setting the VM. This allows us to remove some fields of the PruneFreezeResult. --- src/backend/access/heap/pruneheap.c | 86 ++++++++++++++++++++++------ src/backend/access/heap/vacuumlazy.c | 68 +--------------------- src/include/access/heapam.h | 25 +++----- 3 files changed, 77 insertions(+), 102 deletions(-) diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index 14d40476be9..39149fbba7c 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -918,6 +918,31 @@ heap_page_will_set_vm(PruneState *prstate, return true; } +#ifdef USE_ASSERT_CHECKING + +/* + * Wrapper for heap_page_would_be_all_visible() which can be used for callers + * that expect no LP_DEAD on the page. Currently assert-only, but there is no + * reason not to use it outside of asserts. + */ +static bool +heap_page_is_all_visible(Relation rel, Buffer buf, + TransactionId OldestXmin, + bool *all_frozen, + TransactionId *visibility_cutoff_xid, + OffsetNumber *logging_offnum) +{ + + return heap_page_would_be_all_visible(rel, buf, + OldestXmin, + NULL, 0, + all_frozen, + visibility_cutoff_xid, + logging_offnum); +} +#endif + + /* * Prune and repair fragmentation and potentially freeze tuples on the @@ -971,6 +996,7 @@ heap_page_prune_and_freeze(PruneFreezeParams *params, Buffer vmbuffer = params->vmbuffer; Page page = BufferGetPage(buffer); BlockNumber blockno = BufferGetBlockNumber(buffer); + TransactionId vm_conflict_horizon = InvalidTransactionId; PruneState prstate; bool do_freeze; bool do_prune; @@ -1129,23 +1155,8 @@ heap_page_prune_and_freeze(PruneFreezeParams *params, presult->nfrozen = prstate.nfrozen; presult->live_tuples = prstate.live_tuples; presult->recently_dead_tuples = prstate.recently_dead_tuples; - presult->all_visible = prstate.all_visible; - presult->all_frozen = prstate.all_frozen; presult->hastup = prstate.hastup; - /* - * For callers planning to update the visibility map, the conflict horizon - * for that record must be the newest xmin on the page. However, if the - * page is completely frozen, there can be no conflict and the - * vm_conflict_horizon should remain InvalidTransactionId. This includes - * the case that we just froze all the tuples; the prune-freeze record - * included the conflict XID already so the caller doesn't need it. - */ - if (presult->all_frozen) - presult->vm_conflict_horizon = InvalidTransactionId; - else - presult->vm_conflict_horizon = prstate.visibility_cutoff_xid; - presult->lpdead_items = prstate.lpdead_items; /* the presult->deadoffsets array was already filled in */ @@ -1163,6 +1174,46 @@ heap_page_prune_and_freeze(PruneFreezeParams *params, } } + /* + * If updating the visibility map, the conflict horizon for that record + * must be the newest xmin on the page. However, if the page is + * completely frozen, there can be no conflict and the vm_conflict_horizon + * should remain InvalidTransactionId. This includes the case that we + * just froze all the tuples; the prune-freeze record included the + * conflict XID already so we don't need to again. + */ + if (prstate.all_frozen) + vm_conflict_horizon = InvalidTransactionId; + else + vm_conflict_horizon = prstate.visibility_cutoff_xid; + + /* + * During its second pass over the heap, VACUUM calls + * heap_page_would_be_all_visible() to determine whether a page is + * all-visible and all-frozen. The logic here is similar. After completing + * pruning and freezing, use an assertion to verify that our results + * remain consistent with heap_page_would_be_all_visible(). + */ +#ifdef USE_ASSERT_CHECKING + if (prstate.all_visible) + { + TransactionId debug_cutoff; + bool debug_all_frozen; + + Assert(presult->lpdead_items == 0); + + Assert(heap_page_is_all_visible(params->relation, buffer, + prstate.cutoffs->OldestXmin, + &debug_all_frozen, + &debug_cutoff, off_loc)); + + Assert(prstate.all_frozen == debug_all_frozen); + + Assert(!TransactionIdIsValid(debug_cutoff) || + debug_cutoff == vm_conflict_horizon); + } +#endif + /* Now update the visibility map and PD_ALL_VISIBLE hint */ Assert(!prstate.all_visible || (prstate.lpdead_items == 0)); @@ -1209,12 +1260,11 @@ heap_page_prune_and_freeze(PruneFreezeParams *params, * make everything safe for REDO was logged when the page's tuples * were frozen. */ - Assert(!prstate.all_frozen || - !TransactionIdIsValid(presult->vm_conflict_horizon)); + Assert(!prstate.all_frozen || !TransactionIdIsValid(vm_conflict_horizon)); visibilitymap_set(params->relation, blockno, buffer, InvalidXLogRecPtr, - vmbuffer, presult->vm_conflict_horizon, + vmbuffer, vm_conflict_horizon, new_vmbits); } diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index d5c57516785..61564aea5fd 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -456,20 +456,6 @@ static void dead_items_add(LVRelState *vacrel, BlockNumber blkno, OffsetNumber * static void dead_items_reset(LVRelState *vacrel); static void dead_items_cleanup(LVRelState *vacrel); -#ifdef USE_ASSERT_CHECKING -static bool heap_page_is_all_visible(Relation rel, Buffer buf, - TransactionId OldestXmin, - bool *all_frozen, - TransactionId *visibility_cutoff_xid, - OffsetNumber *logging_offnum); -#endif -static bool heap_page_would_be_all_visible(Relation rel, Buffer buf, - TransactionId OldestXmin, - OffsetNumber *deadoffsets, - int ndeadoffsets, - bool *all_frozen, - TransactionId *visibility_cutoff_xid, - OffsetNumber *logging_offnum); static void update_relstats_all_indexes(LVRelState *vacrel); static void vacuum_error_callback(void *arg); static void update_vacuum_error_info(LVRelState *vacrel, @@ -2005,32 +1991,6 @@ lazy_scan_prune(LVRelState *vacrel, vacrel->new_frozen_tuple_pages++; } - /* - * VACUUM will call heap_page_is_all_visible() during the second pass over - * the heap to determine all_visible and all_frozen for the page -- this - * is a specialized version of the logic from this function. Now that - * we've finished pruning and freezing, make sure that we're in total - * agreement with heap_page_is_all_visible() using an assertion. - */ -#ifdef USE_ASSERT_CHECKING - if (presult.all_visible) - { - TransactionId debug_cutoff; - bool debug_all_frozen; - - Assert(presult.lpdead_items == 0); - - Assert(heap_page_is_all_visible(vacrel->rel, buf, - vacrel->cutoffs.OldestXmin, &debug_all_frozen, - &debug_cutoff, &vacrel->offnum)); - - Assert(presult.all_frozen == debug_all_frozen); - - Assert(!TransactionIdIsValid(debug_cutoff) || - debug_cutoff == presult.vm_conflict_horizon); - } -#endif - /* * Now save details of the LP_DEAD items from the page in vacrel */ @@ -3487,29 +3447,6 @@ dead_items_cleanup(LVRelState *vacrel) vacrel->pvs = NULL; } -#ifdef USE_ASSERT_CHECKING - -/* - * Wrapper for heap_page_would_be_all_visible() which can be used for callers - * that expect no LP_DEAD on the page. Currently assert-only, but there is no - * reason not to use it outside of asserts. - */ -static bool -heap_page_is_all_visible(Relation rel, Buffer buf, - TransactionId OldestXmin, - bool *all_frozen, - TransactionId *visibility_cutoff_xid, - OffsetNumber *logging_offnum) -{ - - return heap_page_would_be_all_visible(rel, buf, - OldestXmin, - NULL, 0, - all_frozen, - visibility_cutoff_xid, - logging_offnum); -} -#endif /* * Check whether the heap page in buf is all-visible except for the dead @@ -3533,15 +3470,12 @@ heap_page_is_all_visible(Relation rel, Buffer buf, * - *logging_offnum: OffsetNumber of current tuple being processed; * used by vacuum's error callback system. * - * Callers looking to verify that the page is already all-visible can call - * heap_page_is_all_visible(). - * * This logic is closely related to heap_prune_record_unchanged_lp_normal(). * If you modify this function, ensure consistency with that code. An * assertion cross-checks that both remain in agreement. Do not introduce new * side-effects. */ -static bool +bool heap_page_would_be_all_visible(Relation rel, Buffer buf, TransactionId OldestXmin, OffsetNumber *deadoffsets, diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index ad2af13ec39..bec2f840102 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -257,8 +257,7 @@ typedef struct PruneFreezeParams * HEAP_PAGE_PRUNE_MARK_UNUSED_NOW indicates that dead items can be set * LP_UNUSED during pruning. * - * HEAP_PAGE_PRUNE_FREEZE indicates that we will also freeze tuples, and - * will return 'all_visible', 'all_frozen' flags to the caller. + * HEAP_PAGE_PRUNE_FREEZE indicates that we will also freeze tuples * * HEAP_PAGE_PRUNE_UPDATE_VM indicates that we will set the page's status * in the VM. @@ -294,21 +293,6 @@ typedef struct PruneFreezeResult int live_tuples; int recently_dead_tuples; - /* - * all_visible and all_frozen indicate if the all-visible and all-frozen - * bits in the visibility map can be set for this page, after pruning. - * - * vm_conflict_horizon is the newest xmin of live tuples on the page. The - * caller can use it as the conflict horizon when setting the VM bits. It - * is only valid if we froze some tuples (nfrozen > 0), and all_frozen is - * true. - * - * These are only set if the HEAP_PRUNE_FREEZE option is set. - */ - bool all_visible; - bool all_frozen; - TransactionId vm_conflict_horizon; - /* * old_vmbits are the state of the all-visible and all-frozen bits in the * visibility map before updating it during phase I of vacuuming. @@ -454,6 +438,13 @@ extern void log_heap_prune_and_freeze(Relation relation, Buffer buffer, /* in heap/vacuumlazy.c */ extern void heap_vacuum_rel(Relation rel, const VacuumParams params, BufferAccessStrategy bstrategy); +extern bool heap_page_would_be_all_visible(Relation rel, Buffer buf, + TransactionId OldestXmin, + OffsetNumber *deadoffsets, + int ndeadoffsets, + bool *all_frozen, + TransactionId *visibility_cutoff_xid, + OffsetNumber *logging_offnum); /* in heap/heapam_visibility.c */ extern bool HeapTupleSatisfiesVisibility(HeapTuple htup, Snapshot snapshot, -- 2.43.0