From 3bd4475e117a30219b28239709741ac60bf7aec2 Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Wed, 23 Sep 2026 10:53:09 -0400 Subject: [PATCH v2 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. Backpatch-through: 19 --- src/backend/access/heap/heapam_xlog.c | 49 ++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/src/backend/access/heap/heapam_xlog.c b/src/backend/access/heap/heapam_xlog.c index fae3b477c09..c65804a6256 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(Relation reln, BlockNumber heap_blkno, + uint8 flags) +{ + Buffer vmbuffer = InvalidBuffer; + + if (visibilitymap_get_status(reln, heap_blkno, &vmbuffer) & flags) + { + LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE); + visibilitymap_clear(reln, 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,22 +73,26 @@ heap_xlog_vm_clear(XLogReaderState *record, Relation reln = CreateFakeRelcacheEntry(target_locator); Buffer vmbuffer = InvalidBuffer; + if (!XLogRecHasBlockRef(record, wal_vm_block_id)) + { + heap_xlog_vm_clear_unregistered(reln, heap_blkno, flags); + FreeFakeRelcacheEntry(reln); + return; + } + /* * If the vmbuffer was registered, use the recovery-specific routines to * read it. These will either apply an FPI or indicate that we should * clear the requested bits ourselves. */ - if (XLogRecHasBlockRef(record, wal_vm_block_id)) + if (XLogReadBufferForRedo(record, wal_vm_block_id, + &vmbuffer) == BLK_NEEDS_REDO) { - if (XLogReadBufferForRedo(record, wal_vm_block_id, - &vmbuffer) == BLK_NEEDS_REDO) - { - if (visibilitymap_clear(reln, heap_blkno, vmbuffer, flags)) - PageSetLSN(BufferGetPage(vmbuffer), lsn); - } - if (BufferIsValid(vmbuffer)) - UnlockReleaseBuffer(vmbuffer); + if (visibilitymap_clear(reln, heap_blkno, vmbuffer, flags)) + PageSetLSN(BufferGetPage(vmbuffer), lsn); } + if (BufferIsValid(vmbuffer)) + UnlockReleaseBuffer(vmbuffer); FreeFakeRelcacheEntry(reln); } -- 2.43.0