From b91ca1dfef3b6b7f4fbbe87ddd86d1f2939ef7f8 Mon Sep 17 00:00:00 2001
From: Fujii Masao <fujii@postgresql.org>
Date: Wed, 24 Jun 2026 18:42:43 +0900
Subject: [PATCH v1] Fix promoted-standby reads of unlogged sequences

---
 src/backend/access/hash/hash_xlog.c    | 29 +++-----------------------
 src/backend/access/transam/xlogutils.c | 26 ++++++++++++++++++++++-
 src/backend/commands/sequence_xlog.c   |  1 +
 src/include/access/xlogutils.h         |  2 ++
 4 files changed, 31 insertions(+), 27 deletions(-)

diff --git a/src/backend/access/hash/hash_xlog.c b/src/backend/access/hash/hash_xlog.c
index 2060620c7de..e9a2b9aa9a7 100644
--- a/src/backend/access/hash/hash_xlog.c
+++ b/src/backend/access/hash/hash_xlog.c
@@ -29,7 +29,6 @@ hash_xlog_init_meta_page(XLogReaderState *record)
 	XLogRecPtr	lsn = record->EndRecPtr;
 	Page		page;
 	Buffer		metabuf;
-	ForkNumber	forknum;
 
 	xl_hash_init_meta_page *xlrec = (xl_hash_init_meta_page *) XLogRecGetData(record);
 
@@ -41,16 +40,7 @@ hash_xlog_init_meta_page(XLogReaderState *record)
 	page = BufferGetPage(metabuf);
 	PageSetLSN(page, lsn);
 	MarkBufferDirty(metabuf);
-
-	/*
-	 * Force the on-disk state of init forks to always be in sync with the
-	 * state in shared buffers.  See XLogReadBufferForRedoExtended.  We need
-	 * special handling for init forks as create index operations don't log a
-	 * full page image of the metapage.
-	 */
-	XLogRecGetBlockTag(record, 0, NULL, &forknum, NULL);
-	if (forknum == INIT_FORKNUM)
-		FlushOneBuffer(metabuf);
+	XLogFlushBufferForRedoIfInit(record, 0, metabuf);
 
 	/* all done */
 	UnlockReleaseBuffer(metabuf);
@@ -68,7 +58,6 @@ hash_xlog_init_bitmap_page(XLogReaderState *record)
 	Page		page;
 	HashMetaPage metap;
 	uint32		num_buckets;
-	ForkNumber	forknum;
 
 	xl_hash_init_bitmap_page *xlrec = (xl_hash_init_bitmap_page *) XLogRecGetData(record);
 
@@ -79,16 +68,7 @@ hash_xlog_init_bitmap_page(XLogReaderState *record)
 	_hash_initbitmapbuffer(bitmapbuf, xlrec->bmsize, true);
 	PageSetLSN(BufferGetPage(bitmapbuf), lsn);
 	MarkBufferDirty(bitmapbuf);
-
-	/*
-	 * Force the on-disk state of init forks to always be in sync with the
-	 * state in shared buffers.  See XLogReadBufferForRedoExtended.  We need
-	 * special handling for init forks as create index operations don't log a
-	 * full page image of the metapage.
-	 */
-	XLogRecGetBlockTag(record, 0, NULL, &forknum, NULL);
-	if (forknum == INIT_FORKNUM)
-		FlushOneBuffer(bitmapbuf);
+	XLogFlushBufferForRedoIfInit(record, 0, bitmapbuf);
 	UnlockReleaseBuffer(bitmapbuf);
 
 	/* add the new bitmap page to the metapage's list of bitmaps */
@@ -109,10 +89,7 @@ hash_xlog_init_bitmap_page(XLogReaderState *record)
 
 		PageSetLSN(page, lsn);
 		MarkBufferDirty(metabuf);
-
-		XLogRecGetBlockTag(record, 1, NULL, &forknum, NULL);
-		if (forknum == INIT_FORKNUM)
-			FlushOneBuffer(metabuf);
+		XLogFlushBufferForRedoIfInit(record, 1, metabuf);
 	}
 	if (BufferIsValid(metabuf))
 		UnlockReleaseBuffer(metabuf);
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index fdc341d8fa4..d8c179c5dcc 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -321,6 +321,28 @@ XLogInitBufferForRedo(XLogReaderState *record, uint8 block_id)
 	return buf;
 }
 
+/*
+ * If a redo routine modified an init fork, flush the buffer immediately.
+ *
+ * At the end of crash recovery the init forks of unlogged relations are
+ * copied to the main fork directly from disk, without going through shared
+ * buffers. Therefore, redo routines that update init forks without
+ * restoring a full-page image must call this after setting the page LSN and
+ * marking the buffer dirty.
+ */
+void
+XLogFlushBufferForRedoIfInit(XLogReaderState *record, uint8 block_id,
+							 Buffer buffer)
+{
+	ForkNumber	forknum;
+
+	Assert(BufferIsValid(buffer));
+
+	XLogRecGetBlockTag(record, block_id, NULL, &forknum, NULL);
+	if (forknum == INIT_FORKNUM)
+		FlushOneBuffer(buffer);
+}
+
 /*
  * XLogReadBufferForRedoExtended
  *		Like XLogReadBufferForRedo, but with extra options.
@@ -398,7 +420,9 @@ XLogReadBufferForRedoExtended(XLogReaderState *record,
 		 * At the end of crash recovery the init forks of unlogged relations
 		 * are copied, without going through shared buffers. So we need to
 		 * force the on-disk state of init forks to always be in sync with the
-		 * state in shared buffers.
+		 * state in shared buffers. Use XLogFlushBufferForRedoIfInit() for
+		 * redo routines that dirty init-fork buffers without restoring a
+		 * full-page image.
 		 */
 		if (forknum == INIT_FORKNUM)
 			FlushOneBuffer(*buf);
diff --git a/src/backend/commands/sequence_xlog.c b/src/backend/commands/sequence_xlog.c
index d0aed48e268..fcb3230cf3b 100644
--- a/src/backend/commands/sequence_xlog.c
+++ b/src/backend/commands/sequence_xlog.c
@@ -63,6 +63,7 @@ seq_redo(XLogReaderState *record)
 
 	memcpy(page, localpage, BufferGetPageSize(buffer));
 	MarkBufferDirty(buffer);
+	XLogFlushBufferForRedoIfInit(record, 0, buffer);
 	UnlockReleaseBuffer(buffer);
 
 	pfree(localpage);
diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h
index b97387c6d4c..0c6c7410069 100644
--- a/src/include/access/xlogutils.h
+++ b/src/include/access/xlogutils.h
@@ -87,6 +87,8 @@ typedef struct ReadLocalXLogPageNoWaitPrivate
 extern XLogRedoAction XLogReadBufferForRedo(XLogReaderState *record,
 											uint8 block_id, Buffer *buf);
 extern Buffer XLogInitBufferForRedo(XLogReaderState *record, uint8 block_id);
+extern void XLogFlushBufferForRedoIfInit(XLogReaderState *record,
+										 uint8 block_id, Buffer buffer);
 extern XLogRedoAction XLogReadBufferForRedoExtended(XLogReaderState *record,
 													uint8 block_id,
 													ReadBufferMode mode, bool get_cleanup_lock,
-- 
2.53.0

