From 1076b3a2af2af3c62d9cdfb4e311dfefa34690b9 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Sun, 6 Sep 2026 15:08:04 +0000 Subject: [PATCH v4 1/3] Add durable unlogged-reset LSN to pg_control Record in pg_control where WAL ended the last time the contents of unlogged relations were discarded, and mirror it in shared memory. Crash recovery sets it to the end of WAL right after ResetUnloggedRelations(); pg_resetwal sets it to the new start of WAL, but only when the reset actually loses data, that is when pg_control had to be guessed at or the server had not shut down cleanly. Resetting the WAL of a cleanly shut down cluster leaves unlogged contents intact, and pg_upgrade depends on that. initdb starts it at the bootstrap checkpoint. Add GetUnloggedPopulatedEpoch(), returning that LSN, to be used in a following commit to stamp when an unlogged matview was populated. A stamp is always below the LSN of the pg_class change carrying it, and any node holding that change resets unlogged storage only after replaying past it, so a stale stamp can never match the current epoch. A reset counter would not do: two nodes can reach the same count independently, for example a standby started without standby.signal after its primary crashed once, and the timeline does not change then. This changes the pg_control layout, so PG_CONTROL_VERSION needs a bump; it is left out here to keep the patch from conflicting on every rebase. --- src/backend/access/transam/xlog.c | 42 +++++++++++++++++++++++++ src/bin/pg_controldata/pg_controldata.c | 2 ++ src/bin/pg_resetwal/pg_resetwal.c | 19 +++++++++++ src/include/access/xlog.h | 1 + src/include/catalog/pg_control.h | 3 ++ 5 files changed, 67 insertions(+) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 9ec0be77ca0..40936547439 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->unloggedResetLSN. */ + pg_atomic_uint64 unloggedResetLSN; + /* Time and LSN of last xlog segment switch. Protected by WALWriteLock. */ pg_time_t lastSegSwitchTime; XLogRecPtr lastSegSwitchLSN; @@ -5229,6 +5232,26 @@ GetFakeLSNForUnloggedRel(void) return pg_atomic_fetch_add_u64(&XLogCtl->unloggedLSN, 1); } +/* + * Epoch stamped into pg_class.relpopulated for a populated unlogged matview: + * the end of WAL at the last reset of unlogged relations. + * + * A node stamps only after its reset, so a stamp is always below the LSN of + * the pg_class change carrying it. Any node holding that change has replayed + * past it, so its own next reset lands beyond the stamp. Hence a stale stamp + * cannot match the current epoch, no matter which node wrote it or on which + * timeline. The epoch is always a real WAL position, so never one of the + * reserved values 0 and 1. + */ +uint64 +GetUnloggedPopulatedEpoch(void) +{ + uint64 epoch = pg_atomic_read_u64(&XLogCtl->unloggedResetLSN); + + Assert(epoch > 1); + return epoch; +} + /* * Auto-tune the number of XLOG buffers. * @@ -5658,6 +5681,7 @@ XLOGShmemInit(void *arg) pg_atomic_init_u64(&XLogCtl->logFlushResult, InvalidXLogRecPtr); pg_atomic_init_u64(&XLogCtl->unloggedLSN, InvalidXLogRecPtr); pg_atomic_init_u64(&XLogCtl->lastChecksumChangeRecPtr, InvalidXLogRecPtr); + pg_atomic_init_u64(&XLogCtl->unloggedResetLSN, InvalidXLogRecPtr); } /* @@ -5820,6 +5844,7 @@ BootStrapXLOG(uint32 data_checksum_version) ControlFile->time = checkPoint.time; ControlFile->checkPoint = checkPoint.redo; ControlFile->checkPointCopy = checkPoint; + ControlFile->unloggedResetLSN = checkPoint.redo; /* some additional ControlFile fields are set in WriteControlFile() */ WriteControlFile(); @@ -6341,6 +6366,9 @@ StartupXLOG(void) pg_atomic_write_membarrier_u64(&XLogCtl->unloggedLSN, FirstNormalUnloggedLSN); + pg_atomic_write_membarrier_u64(&XLogCtl->unloggedResetLSN, + ControlFile->unloggedResetLSN); + /* * 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 @@ -6612,8 +6640,22 @@ StartupXLOG(void) * end-of-recovery steps fail. */ if (InRecovery) + { ResetUnloggedRelations(UNLOGGED_RELATION_INIT); + /* + * That discarded the contents of every unlogged relation, so start a + * new epoch at the end of WAL; see GetUnloggedPopulatedEpoch(). If + * we crash before this reaches disk, the next recovery ends at or + * beyond this point anyway. + */ + LWLockAcquire(ControlFileLock, LW_EXCLUSIVE); + ControlFile->unloggedResetLSN = EndOfLog; + pg_atomic_write_membarrier_u64(&XLogCtl->unloggedResetLSN, + ControlFile->unloggedResetLSN); + LWLockRelease(ControlFileLock); + } + /* * 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 6a0f848d8d0..37d113a9db1 100644 --- a/src/bin/pg_controldata/pg_controldata.c +++ b/src/bin/pg_controldata/pg_controldata.c @@ -295,6 +295,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 relations reset at: %X/%08X\n"), + LSN_FORMAT_ARGS(ControlFile->unloggedResetLSN)); 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 41afc4c1316..88990481182 100644 --- a/src/bin/pg_resetwal/pg_resetwal.c +++ b/src/bin/pg_resetwal/pg_resetwal.c @@ -63,6 +63,7 @@ static ControlFileData ControlFile; /* pg_control values */ static XLogSegNo newXlogSegNo; /* new XLOG segment # */ static bool guessed = false; /* T if we had to guess at any values */ +static bool unlogged_suspect = false; /* T if unlogged data is untrustworthy */ static const char *progname; /* @@ -546,6 +547,14 @@ main(int argc, char *argv[]) exit(1); } + /* + * Decide whether the contents of unlogged relations survive this reset. + * They do if the server shut down cleanly and we did not have to guess at + * pg_control; otherwise they are torn, and nothing resets them later + * because the next startup will not run crash recovery. + */ + unlogged_suspect = guessed || ControlFile.state != DB_SHUTDOWNED; + /* * Else, do the dirty deed. */ @@ -917,6 +926,16 @@ RewriteControlFile(void) ControlFile.state = DB_SHUTDOWNED; ControlFile.checkPoint = ControlFile.checkPointCopy.redo; + + /* + * Resetting the WAL skips the crash recovery that would otherwise reset + * unlogged relations. If their contents cannot be trusted, start a new + * epoch at the new start of WAL, which is past any pre-reset epoch stamp + * so those read as "not populated". + */ + if (unlogged_suspect) + ControlFile.unloggedResetLSN = ControlFile.checkPointCopy.redo; + ControlFile.minRecoveryPoint = InvalidXLogRecPtr; ControlFile.minRecoveryPointTLI = 0; ControlFile.backupStartPoint = InvalidXLogRecPtr; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 7a590b7e1ea..45c187bcdee 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -275,6 +275,7 @@ extern void InitLocalDataChecksumState(void); extern void SetLocalDataChecksumState(uint32 data_checksum_version); extern bool GetDefaultCharSignedness(void); extern XLogRecPtr GetFakeLSNForUnloggedRel(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 c3c934d0012..888b7f8e325 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -143,6 +143,9 @@ typedef struct ControlFileData CheckPoint checkPointCopy; /* copy of last check point record */ XLogRecPtr unloggedLSN; /* current fake LSN value, for unlogged rels */ + XLogRecPtr unloggedResetLSN; /* end of WAL when unlogged relations + * were last reset (end of recovery) or + * became unsafe to trust (pg_resetwal) */ /* * These two values determine the minimum point we must recover up to -- 2.55.0