From ee1d1eabd7cce8217258a85cc43736340eff7005 Mon Sep 17 00:00:00 2001
From: Zsolt Parragi <zsolt.parragi@percona.com>
Date: Thu, 2 Jul 2026 17:48:55 +0000
Subject: [PATCH 1/3] Add durable unlogged-reset generation counter

Bump a control-file counter whenever crash recovery or pg_resetwal
resets unlogged relations, and mirror it in shared memory. Add
GetUnloggedPopulatedEpoch() = (timeline << 32) | reset-gen to stamp
when an unlogged matview was populated.
---
 src/backend/access/transam/xlog.c       | 43 +++++++++++++++++++++++++
 src/bin/pg_controldata/pg_controldata.c |  2 ++
 src/bin/pg_resetwal/pg_resetwal.c       |  8 +++++
 src/include/access/xlog.h               |  2 ++
 src/include/catalog/pg_control.h        |  5 ++-
 5 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index a81912b7441..60c05ee9ce4 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -469,6 +469,9 @@ typedef struct XLogCtlData
 	/* Fake LSN counter, for unlogged relations. */
 	pg_atomic_uint64 unloggedLSN;
 
+	/* Shared-memory mirror of ControlFile->unloggedResetGen. */
+	pg_atomic_uint64 unloggedResetGen;
+
 	/* Time and LSN of last xlog segment switch. Protected by WALWriteLock. */
 	pg_time_t	lastSegSwitchTime;
 	XLogRecPtr	lastSegSwitchLSN;
@@ -4273,6 +4276,7 @@ InitControlFile(uint64 sysidentifier, uint32 data_checksum_version)
 	memcpy(ControlFile->mock_authentication_nonce, mock_auth_nonce, MOCK_AUTH_NONCE_LEN);
 	ControlFile->state = DB_SHUTDOWNED;
 	ControlFile->unloggedLSN = FirstNormalUnloggedLSN;
+	ControlFile->unloggedResetGen = 0;
 
 	/* Set important parameter values for use when replaying WAL */
 	ControlFile->MaxConnections = MaxConnections;
@@ -5009,6 +5013,29 @@ GetFakeLSNForUnloggedRel(void)
 	return pg_atomic_fetch_add_u64(&XLogCtl->unloggedLSN, 1);
 }
 
+/* Cluster-wide unlogged-reset generation, read from the shmem mirror. */
+uint64
+GetUnloggedResetGeneration(void)
+{
+	return pg_atomic_read_u64(&XLogCtl->unloggedResetGen);
+}
+
+/*
+ * Epoch stamped into pg_class.relpopulated for a populated unlogged matview:
+ * (timeline << 32) | reset-gen.  Crash recovery bumps the gen and
+ * promotion/PITR changes the timeline, so either invalidates old stamps.
+ * tli >= 1 keeps the epoch above the reserved values 0 and 1.
+ */
+uint64
+GetUnloggedPopulatedEpoch(void)
+{
+	uint64		gen = GetUnloggedResetGeneration();
+	TimeLineID	tli = GetWALInsertionTimeLine();
+
+	Assert(tli >= 1);
+	return ((uint64) tli << 32) | (uint32) gen;
+}
+
 /*
  * Auto-tune the number of XLOG buffers.
  *
@@ -5435,6 +5462,7 @@ XLOGShmemInit(void *arg)
 	pg_atomic_init_u64(&XLogCtl->logWriteResult, InvalidXLogRecPtr);
 	pg_atomic_init_u64(&XLogCtl->logFlushResult, InvalidXLogRecPtr);
 	pg_atomic_init_u64(&XLogCtl->unloggedLSN, InvalidXLogRecPtr);
+	pg_atomic_init_u64(&XLogCtl->unloggedResetGen, 0);
 }
 
 /*
@@ -6073,6 +6101,10 @@ StartupXLOG(void)
 		pg_atomic_write_membarrier_u64(&XLogCtl->unloggedLSN,
 									   FirstNormalUnloggedLSN);
 
+	/* Mirror the durable unlogged-reset counter into shared memory. */
+	pg_atomic_write_membarrier_u64(&XLogCtl->unloggedResetGen,
+								   ControlFile->unloggedResetGen);
+
 	/*
 	 * Copy any missing timeline history files between 'now' and the recovery
 	 * target timeline from archive to pg_wal. While we don't need those files
@@ -6347,6 +6379,17 @@ StartupXLOG(void)
 	if (InRecovery)
 		ResetUnloggedRelations(UNLOGGED_RELATION_INIT);
 
+	/*
+	 * Crash recovery reset all unlogged relations; bump the durable reset
+	 * generation and refresh the mirror.  ControlFile is persisted below.
+	 */
+	if (InRecovery)
+	{
+		ControlFile->unloggedResetGen++;
+		pg_atomic_write_membarrier_u64(&XLogCtl->unloggedResetGen,
+									   ControlFile->unloggedResetGen);
+	}
+
 	/*
 	 * Pre-scan prepared transactions to find out the range of XIDs present.
 	 * This information is not quite needed yet, but it is positioned here so
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index fe5fc5ec133..07a7edb07ab 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -293,6 +293,8 @@ main(int argc, char *argv[])
 		   ckpttime_str);
 	printf(_("Fake LSN counter for unlogged rels:   %X/%08X\n"),
 		   LSN_FORMAT_ARGS(ControlFile->unloggedLSN));
+	printf(_("Unlogged reset generation:            " UINT64_FORMAT "\n"),
+		   ControlFile->unloggedResetGen);
 	printf(_("Minimum recovery ending location:     %X/%08X\n"),
 		   LSN_FORMAT_ARGS(ControlFile->minRecoveryPoint));
 	printf(_("Min recovery ending loc's timeline:   %u\n"),
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index f8d25afed9d..ec3fc2b8717 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -712,6 +712,7 @@ GuessControlValues(void)
 	ControlFile.time = (pg_time_t) time(NULL);
 	ControlFile.checkPoint = ControlFile.checkPointCopy.redo;
 	ControlFile.unloggedLSN = FirstNormalUnloggedLSN;
+	ControlFile.unloggedResetGen = 0;
 
 	/* minRecoveryPoint, backupStartPoint and backupEndPoint can be left zero */
 
@@ -913,6 +914,13 @@ RewriteControlFile(void)
 
 	ControlFile.state = DB_SHUTDOWNED;
 	ControlFile.checkPoint = ControlFile.checkPointCopy.redo;
+
+	/*
+	 * WAL reset skips crash recovery, so unlogged storage is suspect.  Bump
+	 * the reset generation so pre-reset epoch stamps read as "not populated".
+	 */
+	ControlFile.unloggedResetGen++;
+
 	ControlFile.minRecoveryPoint = InvalidXLogRecPtr;
 	ControlFile.minRecoveryPointTLI = 0;
 	ControlFile.backupStartPoint = InvalidXLogRecPtr;
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 4dd98624204..a77550bfbe8 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -261,6 +261,8 @@ extern void InitLocalDataChecksumState(void);
 extern void SetLocalDataChecksumState(uint32 data_checksum_version);
 extern bool GetDefaultCharSignedness(void);
 extern XLogRecPtr GetFakeLSNForUnloggedRel(void);
+extern uint64 GetUnloggedResetGeneration(void);
+extern uint64 GetUnloggedPopulatedEpoch(void);
 extern void BootStrapXLOG(uint32 data_checksum_version);
 extern void InitializeWalConsistencyChecking(void);
 extern void LocalProcessControlFile(bool reset);
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index 80b3a730e03..8ff1faf3994 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -22,7 +22,7 @@
 
 
 /* Version identifier for this pg_control format */
-#define PG_CONTROL_VERSION	1902
+#define PG_CONTROL_VERSION	1903
 
 /* Nonce key length, see below */
 #define MOCK_AUTH_NONCE_LEN		32
@@ -143,6 +143,9 @@ typedef struct ControlFileData
 	CheckPoint	checkPointCopy; /* copy of last check point record */
 
 	XLogRecPtr	unloggedLSN;	/* current fake LSN value, for unlogged rels */
+	uint64		unloggedResetGen;	/* bumped whenever unlogged relations are
+									 * reset (end of recovery) or become
+									 * unsafe to trust (pg_resetwal) */
 
 	/*
 	 * These two values determine the minimum point we must recover up to
-- 
2.43.0

