From db07f4d74e0632ddd39ea80fa73a719aa8d9a659 Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Wed, 23 Sep 2026 16:58:54 -0400 Subject: [PATCH vmaster2 1/3] Clear divergent visibility map bits during heap redo When replaying a heap record that clears PD_ALL_VISIBLE but did not register a VM buffer because the VM was already clear on the primary, clear the VM bit anyway. The VM can diverge across a cluster, e.g. via CREATE DATABASE STRATEGY WAL_LOG, and a standby must not keep a set VM bit over a page whose PD_ALL_VISIBLE is clear. You can still get torn pages because you didn't log an FPI on the primary, however the VM is usually read with RBM_ZERO_ON_ERROR, so it is considered sufficient. Only backpatch to 19 because <= 18 already have a fallback. The fix differs on master because it no longer makes a fake relcache entry in recovery, so it needed a dedicated function to read the unregistered VM page. This is a stopgap so that the issue is fixed across branches. In the future, master should implement a more robust fix. Backpatch-through: 19 --- src/backend/access/heap/heapam_xlog.c | 30 ++++++++++++++++++++++++ src/backend/access/heap/visibilitymap.c | 31 +++++++++++++++++++++++++ src/include/access/visibilitymap.h | 2 ++ 3 files changed, 63 insertions(+) diff --git a/src/backend/access/heap/heapam_xlog.c b/src/backend/access/heap/heapam_xlog.c index 5fa1de09cfb..e1c68be698d 100644 --- a/src/backend/access/heap/heapam_xlog.c +++ b/src/backend/access/heap/heapam_xlog.c @@ -22,6 +22,33 @@ #include "storage/freespace.h" #include "storage/standby.h" +/* + * Clear visibility map bits for a heap block when the WAL record clearing it + * did not register the VM block. This handles cases where the VM is + * out-of-sync between the primary and standby (for instance, CREATE DATABASE + * STRATEGY WAL_LOG historically could cause this). + * + * This is not fully resilient: the VM page is modified without a full-page + * image, so a torn write during a crash could leave it inconsistent until the + * page is next repaired. That is considered acceptable since the VM is zeroed + * on error when reading it. + */ +static void +heap_xlog_vm_clear_unregistered(RelFileLocator rlocator, BlockNumber heap_blkno, + uint8 flags) +{ + Buffer vmbuffer = InvalidBuffer; + + if (xlog_visibilitymap_get_status(rlocator, heap_blkno, &vmbuffer) & flags) + { + LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE); + visibilitymap_clear(rlocator, heap_blkno, vmbuffer, flags); + UnlockReleaseBuffer(vmbuffer); + } + else if (BufferIsValid(vmbuffer)) + ReleaseBuffer(vmbuffer); +} + /* * Clear visibility map bits for a single heap block during heap redo. * @@ -46,7 +73,10 @@ heap_xlog_vm_clear(XLogReaderState *record, Buffer vmbuffer = InvalidBuffer; if (!XLogRecHasBlockRef(record, wal_vm_block_id)) + { + heap_xlog_vm_clear_unregistered(target_locator, heap_blkno, flags); return; + } /* * If the vmbuffer was registered, use the recovery-specific routines to diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index fe5ce437e1b..d7ba6b8ca20 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -12,6 +12,7 @@ * * INTERFACE ROUTINES * visibilitymap_clear - clear bits for one page in the visibility map + * xlog_visibilitymap_get_status - get status of bits during WAL replay * visibilitymap_pin - pin a map page for setting a bit * visibilitymap_pin_ok - check whether correct map page is already pinned * visibilitymap_set - set bit(s) in a previously pinned page @@ -189,6 +190,36 @@ visibilitymap_clear(RelFileLocator rlocator, BlockNumber heapBlk, return cleared; } +/* + * Like visibilitymap_get_status(), but uses a RelFileLocator instead of a + * Relation, so it needs no relcache entry and can be used by redo routines. + * + * On return *vmbuf holds the pinned (but unlocked) map page; the caller is + * responsible for releasing it. A caller that goes on to clear bits must lock + * it first. + */ +uint8 +xlog_visibilitymap_get_status(RelFileLocator rlocator, BlockNumber heapBlk, + Buffer *vmbuf) +{ + BlockNumber mapBlock = HEAPBLK_TO_MAPBLOCK(heapBlk); + uint32 mapByte = HEAPBLK_TO_MAPBYTE(heapBlk); + uint8 mapOffset = HEAPBLK_TO_OFFSET(heapBlk); + char *map; + + Assert(InRecovery); + + *vmbuf = XLogReadBufferExtended(rlocator, VISIBILITYMAP_FORKNUM, mapBlock, + RBM_ZERO_ON_ERROR, InvalidBuffer); + if (!BufferIsValid(*vmbuf)) + return 0; + + map = PageGetContents(BufferGetPage(*vmbuf)); + + /* A single byte read is atomic (see visibilitymap_get_status()). */ + return ((map[mapByte] >> mapOffset) & VISIBILITYMAP_VALID_BITS); +} + /* * visibilitymap_pin - pin a map page for setting a bit * diff --git a/src/include/access/visibilitymap.h b/src/include/access/visibilitymap.h index 165efd1c00e..47b28f311f4 100644 --- a/src/include/access/visibilitymap.h +++ b/src/include/access/visibilitymap.h @@ -28,6 +28,8 @@ extern bool visibilitymap_clear(RelFileLocator rlocator, BlockNumber heapBlk, Buffer vmbuf, uint8 flags); +extern uint8 xlog_visibilitymap_get_status(RelFileLocator rlocator, + BlockNumber heapBlk, Buffer *vmbuf); extern void visibilitymap_pin(Relation rel, BlockNumber heapBlk, Buffer *vmbuf); extern bool visibilitymap_pin_ok(BlockNumber heapBlk, Buffer vmbuf); -- 2.43.0