On Thu, Sep 10, 2026 at 3:25 PM Melanie Plageman
<[email protected]> wrote:
>
> It also reported that since  d96f87332b3 the FSM will be less up to
> date on the standby since COPY FREEZE criteria for updating the FSM is
>  if (action == BLK_NEEDS_REDO && freespace < BLCKSZ / 5) and the old
> VM record replay code had no such freespace check. I'll post a patch
> to fix that tomorrow. The fix is pretty simple: just update the FSM if
> the heap multi insert record sets the VM.

Here is a patch for this. While writing it, I realized I do not
understand why we don't update the FSM when restoring an FPI --
especially for COPY FREEZE. The FSM won't be up-to-date after
promotion for those pages and vacuum won't scan them so they'll remain
that way forever. Obviously not something to change in backbranches,
but it strikes me as odd that we do that in any cases -- but
especially for COPY FREEZE.

- Melanie
From 822591ad22b93dc0502aa68982bbcc600b5019be Mon Sep 17 00:00:00 2001
From: Melanie Plageman <[email protected]>
Date: Fri, 11 Sep 2026 12:48:29 -0400
Subject: [PATCH v1] Update FSM in COPY FREEZE replay

d96f87332b3 folded COPY FREEZE visibility map updates into
XLOG_HEAP2_MULTI_INSERT records. Unlike the former XLOG_HEAP2_VISIBLE
redo path, multi-insert redo only updated the FSM when the record did
not contain a heap page FPI and the heap page had less than 20% free
space.

This can leave all-frozen pages with missing or stale FSM entries on a
standby. After promotion, vacuum will skip those pages, preventing
their free space from being discovered.

When a multi-insert record sets the page all-frozen, record the heap
page's free space unconditionally.
---
 src/backend/access/heap/heapam_xlog.c | 46 +++++++++++++++++++--------
 1 file changed, 32 insertions(+), 14 deletions(-)

diff --git a/src/backend/access/heap/heapam_xlog.c b/src/backend/access/heap/heapam_xlog.c
index 7a7bc7ea740..440b3b8546f 100644
--- a/src/backend/access/heap/heapam_xlog.c
+++ b/src/backend/access/heap/heapam_xlog.c
@@ -637,8 +637,6 @@ heap_xlog_multi_insert(XLogReaderState *record)
 		if (tupdata != endptr)
 			elog(PANIC, "total tuple length mismatch");
 
-		freespace = PageGetHeapFreeSpace(page); /* needed to update FSM below */
-
 		PageSetLSN(page, lsn);
 
 		if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED)
@@ -660,9 +658,41 @@ heap_xlog_multi_insert(XLogReaderState *record)
 
 		MarkBufferDirty(buffer);
 	}
+
 	if (BufferIsValid(buffer))
+	{
+		/*
+		 * If we are marking the page all-frozen or the page is running low on
+		 * free space, update the FSM as well. Arbitrarily, our definition of
+		 * "low" is less than 20%. We can't do much better than that without
+		 * knowing the fill-factor for the table.
+		 *
+		 * XXX: Unless setting the page all-frozen, we don't do this if the
+		 * page was restored from full page image. We don't bother to update
+		 * the FSM in that case, it doesn't need to be totally accurate
+		 * anyway.
+		 *
+		 * If setting the page all-frozen, we update the FSM regardless since,
+		 * once frozen, we lose the chance to update it during vacuum after
+		 * promotion. See comment in heap_xlog_prune_freeze() for details.
+		 */
+		bool		update_fsm = false;
+
+		if (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET ||
+			action == BLK_NEEDS_REDO)
+		{
+			freespace = PageGetHeapFreeSpace(BufferGetPage(buffer));
+			if (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET ||
+				freespace < BLCKSZ / 5)
+				update_fsm = true;
+		}
+
 		UnlockReleaseBuffer(buffer);
 
+		if (update_fsm)
+			XLogRecordPageWithFreeSpace(rlocator, blkno, freespace);
+	}
+
 	buffer = InvalidBuffer;
 
 	/*
@@ -709,18 +739,6 @@ heap_xlog_multi_insert(XLogReaderState *record)
 
 	if (BufferIsValid(vmbuffer))
 		UnlockReleaseBuffer(vmbuffer);
-
-	/*
-	 * If the page is running low on free space, update the FSM as well.
-	 * Arbitrarily, our definition of "low" is less than 20%. We can't do much
-	 * better than that without knowing the fill-factor for the table.
-	 *
-	 * XXX: Don't do this if the page was restored from full page image. We
-	 * don't bother to update the FSM in that case, it doesn't need to be
-	 * totally accurate anyway.
-	 */
-	if (action == BLK_NEEDS_REDO && freespace < BLCKSZ / 5)
-		XLogRecordPageWithFreeSpace(rlocator, blkno, freespace);
 }
 
 /*
-- 
2.43.0

Reply via email to