Hi, At present time, an existing pg_is_in_recovery() method is not enoughto distinguish a server being in point in time recovery (PITR) mode and an ordinary replica
because it returns true in both cases.
That is why pg_is_standby_requested() function introduced in attached patch might help. It reports whether a standby.signal file was found in the data directory at startup process.
Instructions for reproducing the possible use case are also attached. Hope it will be usefull. Respectfully, Mikhail Litsarev Postgres Professional: https://postgrespro.com
Steps to reproduce use case: 1. Log in as a postgres user and setup env variables (PGDATA, PGLOG, PGPORT etc.), also mkdir $PGDATA/../archive_dir 2. Create data directory initdb -k 3. Modify postgresql.conf wal_level = replica archive_mode = on archive_command = 'test ! -f /home/postgres/data/pgpro/archive_dir/%f && cp %p /home/postgres/data/pgpro/archive_dir/%f' 4. Start server pg_ctl start -l $PGLOG/logfile 5. Create example database and run psql createdb db_pitr_01 psql -d db_pitr_1 6. Create table (just to modify database) CREATE TABLE t8x1_float4 AS SELECT random()::float4 as a1 FROM generate_series(1, 8); 7. Switch to a new WAL file SELECT pg_switch_wal(); 8. Make a backup pg_basebackup -Ft -X none -D - | gzip > /home/postgres/data/pgpro/db_file_backup.tar.gz 9. Update table INSERT INTO t8x1_float4 values (1.); INSERT INTO t8x1_float4 values (2.); INSERT INTO t8x1_float4 values (3.); 10. Switch to a new WAL file SELECT pg_switch_wal(); 11. Stop database and remove data directory pg_ctl stop rm -r /home/postgres/data/pgpro/data-debug/* 12. Restore data directory tar xvfz db_file_backup.tar.gz -C /home/postgres/data/pgpro/data-debug/ 13. Update the following parameters in postgrespro.conf recovery_target_time = '2024-03-26 10:26:30 GMT' restore_command = 'cp /home/postgres/data/pgpro/archive_dir/%f %p' 14. touch recovery.signal 15. pg_ctl start -l $PGLOG/logfile 16. Start database and see it is in Point In Time Recovery mode psql -d db_pitr_01 db_pitr_01=# SELECT pg_is_in_recovery(); pg_is_in_recovery ------------------- t (1 row) db_pitr_01=# SELECT pg_is_standby_requested(); pg_is_standby_requested ------------------------- f (1 row) Now we can differ recovering server in Point In Time Recovery mode from standby mode. pg_is_standby_requested() returns true for a replica.
From 563431ffb53d0b598f33d3378b7ba40338020ca6 Mon Sep 17 00:00:00 2001 From: Mikhail Litsarev <m.litsa...@postgrespro.ru> Date: Tue, 20 Feb 2024 20:05:37 +0300 Subject: [PATCH 1/2] Introduce pg_is_standby_requested(). Introduce pg_is_standby_requested() function to distinguish a replica from a regular instance in point in time recovery mode. --- src/backend/access/transam/xlogfuncs.c | 14 ++++++++++ src/backend/access/transam/xlogrecovery.c | 33 +++++++++++++++++++++++ src/include/access/xlogrecovery.h | 1 + src/include/catalog/pg_proc.dat | 4 +++ 4 files changed, 52 insertions(+) diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index 45452d937c7..3170c4ef343 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -753,3 +753,17 @@ pg_promote(PG_FUNCTION_ARGS) wait_seconds))); PG_RETURN_BOOL(false); } + +/* + * Returns bool with a current value of StandbyModeIsRequested flag + * to distinguish a replica from a regular instance in a + * Point In Time Recovery (PITR) mode. + * + * Returns true if standby.signal file is found at startup process + * false otherwise + */ +Datum +pg_is_standby_requested(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(StandbyModeIsRequested()); +} diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c index 6318878356a..cbca4c5055d 100644 --- a/src/backend/access/transam/xlogrecovery.c +++ b/src/backend/access/transam/xlogrecovery.c @@ -321,6 +321,12 @@ typedef struct XLogRecoveryCtlData */ bool SharedPromoteIsTriggered; + /* + * SharedStandbyModeRequested indicates if we're in a standby mode at + * start, while recovery mode is on. Protected by info_lck. + */ + bool SharedStandbyModeRequested; + /* * 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 @@ -1070,7 +1076,16 @@ readRecoverySignalFile(void) ArchiveRecoveryRequested = true; } else + { + /* + * There is no need to use Spinlock here because only the startup + * process modifies the SharedStandbyModeRequested variable here and + * no other processes are reading it at that time. + */ + XLogRecoveryCtl->SharedStandbyModeRequested = StandbyModeRequested; return; + } + XLogRecoveryCtl->SharedStandbyModeRequested = StandbyModeRequested; /* * We don't support standby mode in standalone backends; that requires @@ -4555,6 +4570,24 @@ HotStandbyActiveInReplay(void) return LocalHotStandbyActive; } + +/* + * It reports wether a standby.signal file is in the data directory + * at startup. + * + * This works in any process that's connected to shared memory. + */ +bool +StandbyModeIsRequested(void) +{ + /* + * Spinlock is not needed here because SharedStandbyModeRequested variable + * can only be read after startup process is done and no other actions (in + * functions, in processes) change it then. + */ + return XLogRecoveryCtl->SharedStandbyModeRequested; +} + /* * Get latest redo apply position. * diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h index 66affd2eac3..f85a2af6a23 100644 --- a/src/include/access/xlogrecovery.h +++ b/src/include/access/xlogrecovery.h @@ -145,6 +145,7 @@ extern TimestampTz GetLatestXTime(void); extern TimestampTz GetCurrentChunkReplayStartTime(void); extern XLogRecPtr GetCurrentReplayRecPtr(TimeLineID *replayEndTLI); +extern bool StandbyModeIsRequested(void); extern bool PromoteIsTriggered(void); extern bool CheckPromoteSignal(void); extern void WakeupRecovery(void); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index bb775ce7264..a2ac4160d8c 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6494,6 +6494,10 @@ { oid => '3810', descr => 'true if server is in recovery', proname => 'pg_is_in_recovery', provolatile => 'v', prorettype => 'bool', proargtypes => '', prosrc => 'pg_is_in_recovery' }, +{ oid => '8439', + descr => 'Is StandbyMode requested at startup while being in recovery mode', + proname => 'pg_is_standby_requested', provolatile => 'v', prorettype => 'bool', + proargtypes => '', prosrc => 'pg_is_standby_requested' }, { oid => '3820', descr => 'current wal flush location', proname => 'pg_last_wal_receive_lsn', provolatile => 'v', -- 2.34.1 From 3d944423ed1c0f2a73e4a38a61ac545f0450a70d Mon Sep 17 00:00:00 2001 From: Mikhail Litsarev <m.litsa...@postgrespro.ru> Date: Tue, 27 Feb 2024 14:08:42 +0300 Subject: [PATCH 2/2] Test pg_is_standby_requested() in different use-cases. Add tiny tests in src/test/recovery/t/004_timeline_switch.pl They validate: - master is not a replica - standby_1 is a replica - promoted replica in master mode - standby_2 remains a replica after switch to promoted master --- src/test/recovery/t/004_timeline_switch.pl | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl index edaef918454..e3f320ab9ab 100644 --- a/src/test/recovery/t/004_timeline_switch.pl +++ b/src/test/recovery/t/004_timeline_switch.pl @@ -18,6 +18,11 @@ my $node_primary = PostgreSQL::Test::Cluster->new('primary'); $node_primary->init(allows_streaming => 1); $node_primary->start; +# Validate pg_is_standby_requested() for master +my $ret_mode_primary = $node_primary->safe_psql('postgres', + 'SELECT pg_is_standby_requested()'); +is($ret_mode_primary, 'f', "master is not a replica"); + # Take backup my $backup_name = 'my_backup'; $node_primary->backup($backup_name); @@ -32,6 +37,11 @@ $node_standby_2->init_from_backup($node_primary, $backup_name, has_streaming => 1); $node_standby_2->start; +# Validate pg_is_standby_requested() for replica node +my $ret_mode_standby_1 = $node_standby_1->safe_psql('postgres', + 'SELECT pg_is_standby_requested()'); +is($ret_mode_standby_1, 't', "node_standby_1 is a replica"); + # Create some content on primary $node_primary->safe_psql('postgres', "CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a"); @@ -50,6 +60,14 @@ $node_standby_1->psql( stdout => \$psql_out); is($psql_out, 't', "promotion of standby with pg_promote"); +# Validate pg_is_standby_requested() for master promoted from standby node. +# pg_is_standby_requested() returns true because standby.signal file +# was found while being a replica. +# Use it with pg_is_in_recovery() (returns false), for such use-cases. +my $ret_mode_1 = $node_standby_1->safe_psql('postgres', + 'SELECT pg_is_standby_requested()'); +is($ret_mode_1, 't', "node_standby_1 becomes a master"); + # Switch standby 2 to replay from standby 1 my $connstr_1 = $node_standby_1->connstr; $node_standby_2->append_conf( @@ -58,6 +76,11 @@ primary_conninfo='$connstr_1' )); $node_standby_2->restart; +# Validate pg_is_standby_requested() for second replica after restart +my $ret_mode_standby_2 = $node_standby_2->safe_psql('postgres', + 'SELECT pg_is_standby_requested()'); +is($ret_mode_standby_2, 't', "node_standby_2 remains a replica"); + # Insert some data in standby 1 and check its presence in standby 2 # to ensure that the timeline switch has been done. $node_standby_1->safe_psql('postgres', -- 2.34.1