From db07f4d74e0632ddd39ea80fa73a719aa8d9a659 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
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

