From a13039bb1d60e7bdf2d9aa949ffbb16bb0f1f2c3 Mon Sep 17 00:00:00 2001 From: Nitin Jadhav Date: Thu, 24 Sep 2026 07:01:23 +0000 Subject: [PATCH] Expose redo start information in pg_stat_recovery PostgreSQL logs the location where WAL redo starts, but the same origin is not available through pg_stat_recovery. Expose the time, LSN, and timeline of the first record selected for replay. The fields remain NULL until a record is selected and stay NULL when redo is not required. A recovery test verifies that redo_start_lsn matches the existing server log message. --- doc/src/sgml/monitoring.sgml | 41 +++++++++++++++++++++-- src/backend/access/transam/xlogfuncs.c | 21 ++++++++++++ src/backend/access/transam/xlogrecovery.c | 11 ++++++ src/backend/catalog/system_views.sql | 5 ++- src/include/access/xlogrecovery.h | 5 +++ src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_proc.dat | 6 ++-- src/test/recovery/t/001_stream_rep.pl | 11 ++++++ src/test/regress/expected/rules.out | 7 ++-- 9 files changed, 99 insertions(+), 10 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 0d038de1a2..89f7586e0e 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -2147,9 +2147,9 @@ description | Waiting for a newly initialized WAL file to reach durable storage - - - last_replayed_read_lsn pg_lsn + + + last_replayed_read_lsn pg_lsn Start write-ahead log location of the last successfully replayed @@ -2250,6 +2250,41 @@ description | Waiting for a newly initialized WAL file to reach durable storage + + + redo_start_time timestamp with time zone + + + Time when WAL redo began for the current recovery session. NULL until + the first WAL record is selected for replay, and remains NULL if no WAL + record requires redo. + + + + + + redo_start_lsn pg_lsn + + + Start write-ahead log location of the first record replayed during the + current recovery session. This is the location reported by the + redo starts at server log message. NULL until the + first WAL record is selected for replay, and remains NULL if no WAL + record requires redo. + + + + + + redo_start_tli integer + + + Timeline of the first WAL record replayed during the current recovery + session. NULL until the first WAL record is selected for replay, and + remains NULL if no WAL record requires redo. + + + diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index 1f52bf7b42..db22b4e5cc 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -783,6 +783,9 @@ pg_stat_get_recovery(PG_FUNCTION_ARGS) /* Local copies of shared state */ bool promote_triggered; + TimestampTz redo_start_time; + XLogRecPtr redo_start_lsn; + TimeLineID redo_start_tli; XLogRecPtr last_replayed_read_lsn; XLogRecPtr last_replayed_end_lsn; TimeLineID last_replayed_tli; @@ -801,6 +804,9 @@ pg_stat_get_recovery(PG_FUNCTION_ARGS) /* Take a lock to ensure value consistency */ SpinLockAcquire(&XLogRecoveryCtl->info_lck); promote_triggered = XLogRecoveryCtl->SharedPromoteIsTriggered; + redo_start_time = XLogRecoveryCtl->redoStartTime; + redo_start_lsn = XLogRecoveryCtl->redoStartLSN; + redo_start_tli = XLogRecoveryCtl->redoStartTLI; last_replayed_read_lsn = XLogRecoveryCtl->lastReplayedReadRecPtr; last_replayed_end_lsn = XLogRecoveryCtl->lastReplayedEndRecPtr; last_replayed_tli = XLogRecoveryCtl->lastReplayedTLI; @@ -856,5 +862,20 @@ pg_stat_get_recovery(PG_FUNCTION_ARGS) values[8] = CStringGetTextDatum(GetRecoveryPauseStateString(pause_state)); + if (redo_start_time != 0) + values[9] = TimestampTzGetDatum(redo_start_time); + else + nulls[9] = true; + + if (XLogRecPtrIsValid(redo_start_lsn)) + values[10] = LSNGetDatum(redo_start_lsn); + else + nulls[10] = true; + + if (redo_start_tli != 0) + values[11] = Int32GetDatum(redo_start_tli); + else + nulls[11] = true; + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); } diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c index fff8d57ac6..feb88f3050 100644 --- a/src/backend/access/transam/xlogrecovery.c +++ b/src/backend/access/transam/xlogrecovery.c @@ -1635,6 +1635,9 @@ PerformWalRecovery(void) * checkpoint record itself, if it's a shutdown checkpoint). */ SpinLockAcquire(&XLogRecoveryCtl->info_lck); + XLogRecoveryCtl->redoStartTime = 0; + XLogRecoveryCtl->redoStartLSN = InvalidXLogRecPtr; + XLogRecoveryCtl->redoStartTLI = 0; if (RedoStartLSN < CheckPointLoc) { XLogRecoveryCtl->lastReplayedReadRecPtr = InvalidXLogRecPtr; @@ -1701,6 +1704,7 @@ PerformWalRecovery(void) if (record != NULL) { + TimestampTz redoStartTime; TimestampTz xtime; PGRUsage ru0; @@ -1710,6 +1714,13 @@ PerformWalRecovery(void) RmgrStartup(); + redoStartTime = GetCurrentTimestamp(); + SpinLockAcquire(&XLogRecoveryCtl->info_lck); + XLogRecoveryCtl->redoStartTime = redoStartTime; + XLogRecoveryCtl->redoStartLSN = xlogreader->ReadRecPtr; + XLogRecoveryCtl->redoStartTLI = replayTLI; + SpinLockRelease(&XLogRecoveryCtl->info_lck); + ereport(LOG, errmsg("redo starts at %X/%08X", LSN_FORMAT_ARGS(xlogreader->ReadRecPtr))); diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index ad340887f5..1553201998 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1042,7 +1042,10 @@ CREATE VIEW pg_stat_recovery AS s.replay_end_tli, s.recovery_last_xact_time, s.current_chunk_start_time, - s.pause_state + s.pause_state, + s.redo_start_time, + s.redo_start_lsn, + s.redo_start_tli FROM pg_stat_get_recovery() s WHERE s.promote_triggered IS NOT NULL; diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h index 8786b6d3a8..df2f566a3e 100644 --- a/src/include/access/xlogrecovery.h +++ b/src/include/access/xlogrecovery.h @@ -83,6 +83,11 @@ typedef struct XLogRecoveryCtlData */ bool SharedRecoverySubtransInitialized; + /* Origin of WAL redo in the current recovery session. */ + TimestampTz redoStartTime; + XLogRecPtr redoStartLSN; + TimeLineID redoStartTLI; + /* * recoveryWakeupLatch is used to wake up the startup process to continue * WAL replay, if it is waiting for WAL to arrive or promotion to be diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 6f3e526de9..8d366ae371 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202609152 +#define CATALOG_VERSION_NO 202609241 #endif diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index f46427258e..a5aae888c4 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5722,9 +5722,9 @@ { oid => '6514', descr => 'statistics: information about WAL recovery', proname => 'pg_stat_get_recovery', proisstrict => 'f', provolatile => 's', proparallel => 'r', prorettype => 'record', proargtypes => '', - proallargtypes => '{bool,pg_lsn,pg_lsn,int4,pg_lsn,int4,timestamptz,timestamptz,text}', - proargmodes => '{o,o,o,o,o,o,o,o,o}', - proargnames => '{promote_triggered,last_replayed_read_lsn,last_replayed_end_lsn,last_replayed_tli,replay_end_lsn,replay_end_tli,recovery_last_xact_time,current_chunk_start_time,pause_state}', + proallargtypes => '{bool,pg_lsn,pg_lsn,int4,pg_lsn,int4,timestamptz,timestamptz,text,timestamptz,pg_lsn,int4}', + proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o}', + proargnames => '{promote_triggered,last_replayed_read_lsn,last_replayed_end_lsn,last_replayed_tli,replay_end_lsn,replay_end_tli,recovery_last_xact_time,current_chunk_start_time,pause_state,redo_start_time,redo_start_lsn,redo_start_tli}', prosrc => 'pg_stat_get_recovery' }, { oid => '6169', descr => 'statistics: information about replication slot', proname => 'pg_stat_get_replication_slot', provolatile => 's', diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl index a4fa4b96c6..8999fac82d 100644 --- a/src/test/recovery/t/001_stream_rep.pl +++ b/src/test/recovery/t/001_stream_rep.pl @@ -87,6 +87,17 @@ $result = $node_standby_1->safe_psql('postgres', ); is($result, qq(1), 'check recovery state on standby 1'); +$result = $node_standby_1->safe_psql( + 'postgres', + "SELECT redo_start_time IS NOT NULL, redo_start_lsn IS NOT NULL, redo_start_tli > 0 FROM pg_stat_recovery" +); +is($result, qq(t|t|t), 'check recovery redo origin'); + +$result = $node_standby_1->safe_psql('postgres', + "SELECT redo_start_lsn FROM pg_stat_recovery"); +ok($node_standby_1->log_contains("redo starts at $result"), + 'redo start LSN matches the server log'); + # Likewise, but for a sequence $node_primary->safe_psql('postgres', "CREATE SEQUENCE seq1; SELECT nextval('seq1')"); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 4a8cc759d7..268865fb96 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2225,8 +2225,11 @@ pg_stat_recovery| SELECT promote_triggered, replay_end_tli, recovery_last_xact_time, current_chunk_start_time, - pause_state - FROM pg_stat_get_recovery() s(promote_triggered, last_replayed_read_lsn, last_replayed_end_lsn, last_replayed_tli, replay_end_lsn, replay_end_tli, recovery_last_xact_time, current_chunk_start_time, pause_state) + pause_state, + redo_start_time, + redo_start_lsn, + redo_start_tli + FROM pg_stat_get_recovery() s(promote_triggered, last_replayed_read_lsn, last_replayed_end_lsn, last_replayed_tli, replay_end_lsn, replay_end_tli, recovery_last_xact_time, current_chunk_start_time, pause_state, redo_start_time, redo_start_lsn, redo_start_tli) WHERE (promote_triggered IS NOT NULL); pg_stat_recovery_prefetch| SELECT stats_reset, prefetch, -- 2.43.0