From 9750354f2b7d7bd3afd38fca5e0ca2dd814a19a2 Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Tue, 17 Jun 2025 17:22:10 -0400 Subject: [PATCH v1 04/14] Introduce unlogged versions of VM functions Future commits will eliminate usages of xl_heap_visible and incorporate setting the VM into the WAL records making other changes to the heap page. As a step toward this make versions of the functions which update the VM and its heap-specific wrapper which do not emit their own WAL. These will be used in follow-on commits. --- src/backend/access/heap/heapam.c | 44 ++++++++++++++++++++++++ src/backend/access/heap/visibilitymap.c | 45 +++++++++++++++++++++++++ src/include/access/heapam.h | 3 ++ src/include/access/visibilitymap.h | 2 ++ 4 files changed, 94 insertions(+) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index dc409fd3a60..15dc3d88843 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -7868,6 +7868,50 @@ heap_page_set_vm_and_log(Relation rel, BlockNumber heap_blk, Buffer heap_buf, InvalidXLogRecPtr, vmbuf, cutoff_xid, vmflags, set_heap_lsn); } +/* + * Ensure the provided heap page is marked PD_ALL_VISIBLE and then set the + * provided vmflags in the provided vmbuf. + * + * Both the heap page and VM page should be pinned and exclusive locked. + * You must pass a VM buffer containing the correct page of the map + * corresponding to the passed in heap block. + * + * This should only be called in a critical section that also emits WAL (as + * needed) for both heap page changes and VM page changes. + */ +uint8 +heap_page_set_vm(Relation rel, BlockNumber heap_blk, Buffer heap_buf, + Buffer vmbuf, uint8 vmflags) +{ + Page heap_page = BufferGetPage(heap_buf); + + Assert(BufferIsValid(heap_buf)); + Assert(CritSectionCount > 0); + + /* Check that we have the right heap page pinned */ + if (BufferGetBlockNumber(heap_buf) != heap_blk) + elog(ERROR, "wrong heap buffer passed to heap_page_set_vm"); + + /* + * We must never end up with the VM bit set and the page-level + * PD_ALL_VISIBLE bit clear. If that were to occur, a subsequent page + * modification would fail to clear the VM bit. + * + * Prior to Postgres 19, it was possible for the page-level bit to be set + * and the VM bit to be clear. This could happen if we crashed after + * setting PD_ALL_VISIBLE but before setting bits in the VM. Since + * Postgres 19, since heap page modifications are done in the same + * critical section as setting the VM bits, that should not longer happen. + */ + if (!PageIsAllVisible(heap_page)) + { + PageSetAllVisible(heap_page); + MarkBufferDirty(heap_buf); + } + + return visibilitymap_set_vmbyte(rel, heap_blk, vmbuf, vmflags); +} + /* * heap_tuple_should_freeze * diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 45721399122..9f27ace0e1c 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -317,6 +317,51 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, return status; } +/* + * Set flags in the VM block contained in the passed in vmBuf. + * Caller must have pinned and exclusive locked the correct block of the VM in + * vmBuf. + * Caller is responsible for WAL logging the changes to the VM buffer and for + * making any changes needed to the associated heap page. + */ +uint8 +visibilitymap_set_vmbyte(Relation rel, BlockNumber heapBlk, + Buffer vmBuf, uint8 flags) +{ + BlockNumber mapBlock = HEAPBLK_TO_MAPBLOCK(heapBlk); + uint32 mapByte = HEAPBLK_TO_MAPBYTE(heapBlk); + uint8 mapOffset = HEAPBLK_TO_OFFSET(heapBlk); + Page page; + uint8 *map; + uint8 status; + +#ifdef TRACE_VISIBILITYMAP + elog(DEBUG1, "vm_set %s %d", RelationGetRelationName(rel), heapBlk); +#endif + + /* Flags should be valid. Also never clear bits with this function */ + Assert((flags & VISIBILITYMAP_VALID_BITS) == flags); + + /* Must never set all_frozen bit without also setting all_visible bit */ + Assert(flags != VISIBILITYMAP_ALL_FROZEN); + + /* Check that we have the right VM page pinned */ + if (!BufferIsValid(vmBuf) || BufferGetBlockNumber(vmBuf) != mapBlock) + elog(ERROR, "wrong VM buffer passed to visibilitymap_set"); + + page = BufferGetPage(vmBuf); + map = (uint8 *) PageGetContents(page); + + status = (map[mapByte] >> mapOffset) & VISIBILITYMAP_VALID_BITS; + if (flags != status) + { + map[mapByte] |= (flags << mapOffset); + MarkBufferDirty(vmBuf); + } + + return status; +} + /* * visibilitymap_get_status - get status of bits * diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 9375296062f..5127fdb9c77 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -360,6 +360,9 @@ extern bool heap_tuple_should_freeze(HeapTupleHeader tuple, TransactionId *NoFreezePageRelfrozenXid, MultiXactId *NoFreezePageRelminMxid); extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple); + +extern uint8 heap_page_set_vm(Relation rel, BlockNumber heap_blk, Buffer heap_buf, + Buffer vmbuf, uint8 vmflags); extern uint8 heap_page_set_vm_and_log(Relation rel, BlockNumber heap_blk, Buffer heap_buf, Buffer vmbuf, TransactionId cutoff_xid, uint8 vmflags); diff --git a/src/include/access/visibilitymap.h b/src/include/access/visibilitymap.h index 4fa4f837535..5d0a9417c25 100644 --- a/src/include/access/visibilitymap.h +++ b/src/include/access/visibilitymap.h @@ -37,6 +37,8 @@ extern uint8 visibilitymap_set(Relation rel, Buffer vmBuf, TransactionId cutoff_xid, uint8 flags, bool set_heap_lsn); +extern uint8 visibilitymap_set_vmbyte(Relation rel, BlockNumber heapBlk, + Buffer vmBuf, uint8 flags); extern uint8 visibilitymap_get_status(Relation rel, BlockNumber heapBlk, Buffer *vmbuf); extern void visibilitymap_count(Relation rel, BlockNumber *all_visible, BlockNumber *all_frozen); extern BlockNumber visibilitymap_prepare_truncate(Relation rel, -- 2.34.1