simple there are already too many of them. Perhaps we should begin
tracking all that as a set of bitmasks, then plug in the tracked state
in shmem for consumption in some SQL function.
Hi!
Michael, Tristan
as a first step I have introduced the `SharedRecoveryDataFlags` bitmask
instead of three boolean SharedHotStandbyActive,
SharedPromoteIsTriggered and SharedStandbyModeRequested flags (the last
one from my previous patch) and made minimal updates in corresponding
code based on that change.
Respectfully,
Mikhail Litsarev
Postgres Professional: https://postgrespro.com
From e05e86baf90a14efec29b0efcb5696504796af9b Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsa...@postgrespro.ru>
Date: Mon, 6 May 2024 15:23:52 +0300
Subject: [PATCH 1/2] Introduce pg_is_standby_requested().
Introduce a bit array SharedRecoveryDataFlags and
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 | 75 ++++++++++++++++++-----
src/include/access/xlogrecovery.h | 1 +
src/include/catalog/pg_proc.dat | 4 ++
4 files changed, 79 insertions(+), 15 deletions(-)
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 92bdb17ed52..3ce934fcedb 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -747,3 +747,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 29c5bec0847..5337c4974f0 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -170,13 +170,13 @@ static XLogRecPtr RedoStartLSN = InvalidXLogRecPtr;
static TimeLineID RedoStartTLI = 0;
/*
- * Local copy of SharedHotStandbyActive variable. False actually means "not
+ * Local copy of XLR_HOT_STANDBY_ACTIVE flag. False actually means "not
* known, need to check the shared state".
*/
static bool LocalHotStandbyActive = false;
/*
- * Local copy of SharedPromoteIsTriggered variable. False actually means "not
+ * Local copy of XLR_PROMOTE_IS_TRIGGERED flag. False actually means "not
* known, need to check the shared state".
*/
static bool LocalPromoteIsTriggered = false;
@@ -298,22 +298,42 @@ static char *replay_image_masked = NULL;
static char *primary_image_masked = NULL;
+/*
+ * The flag indicates if we allow hot standby queries to be
+ * run. Protected by info_lck.
+ */
+#define XLR_HOT_STANDBY_ACTIVE 0x01
+/*
+ * The flag indicates if a standby promotion has been
+ * triggered. Protected by info_lck.
+ */
+#define XLR_PROMOTE_IS_TRIGGERED 0x02
+/*
+ * The flag indicates if we're in a standby mode at
+ * start, while recovery mode is on.
+ */
+#define XLR_STANDBY_MODE_REQUESTED 0x04
+
/*
* Shared-memory state for WAL recovery.
*/
typedef struct XLogRecoveryCtlData
{
/*
- * SharedHotStandbyActive indicates if we allow hot standby queries to be
- * run. Protected by info_lck.
- */
- bool SharedHotStandbyActive;
-
- /*
- * SharedPromoteIsTriggered indicates if a standby promotion has been
- * triggered. Protected by info_lck.
+ * This bit array is introduced to keep the following states:
+ *
+ * -- XLR_HOT_STANDBY_ACTIVE indicates if we allow hot standby queries
+ * to be run. Protected by info_lck.
+ *
+ * -- XLR_PROMOTE_IS_TRIGGERED indicates if a standby promotion
+ * has been triggered. Protected by info_lck.
+ *
+ * -- XLR_STANDBY_MODE_REQUESTED indicates if we're in a standby mode
+ * at start, while recovery mode is on. No info_lck protection.
+ *
+ * and can be extended in future.
*/
- bool SharedPromoteIsTriggered;
+ uint32 SharedRecoveryDataFlags;
/*
* recoveryWakeupLatch is used to wake up the startup process to continue
@@ -1082,10 +1102,17 @@ readRecoverySignalFile(void)
StandbyModeRequested = false;
ArchiveRecoveryRequested = false;
+ /*
+ * There is no need to use Spinlock here because only the startup
+ * process modifies the SharedRecoveryDataFlags bit here and
+ * no other processes are reading it at that time.
+ */
+ XLogRecoveryCtl->SharedRecoveryDataFlags &= ~XLR_STANDBY_MODE_REQUESTED;
if (standby_signal_file_found)
{
StandbyModeRequested = true;
ArchiveRecoveryRequested = true;
+ XLogRecoveryCtl->SharedRecoveryDataFlags |= XLR_STANDBY_MODE_REQUESTED;
}
else if (recovery_signal_file_found)
{
@@ -2259,7 +2286,7 @@ CheckRecoveryConsistency(void)
IsUnderPostmaster)
{
SpinLockAcquire(&XLogRecoveryCtl->info_lck);
- XLogRecoveryCtl->SharedHotStandbyActive = true;
+ XLogRecoveryCtl->SharedRecoveryDataFlags |= XLR_HOT_STANDBY_ACTIVE;
SpinLockRelease(&XLogRecoveryCtl->info_lck);
LocalHotStandbyActive = true;
@@ -4402,7 +4429,8 @@ PromoteIsTriggered(void)
return true;
SpinLockAcquire(&XLogRecoveryCtl->info_lck);
- LocalPromoteIsTriggered = XLogRecoveryCtl->SharedPromoteIsTriggered;
+ LocalPromoteIsTriggered =
+ (XLogRecoveryCtl->SharedRecoveryDataFlags & XLR_PROMOTE_IS_TRIGGERED) != 0;
SpinLockRelease(&XLogRecoveryCtl->info_lck);
return LocalPromoteIsTriggered;
@@ -4412,7 +4440,7 @@ static void
SetPromoteIsTriggered(void)
{
SpinLockAcquire(&XLogRecoveryCtl->info_lck);
- XLogRecoveryCtl->SharedPromoteIsTriggered = true;
+ XLogRecoveryCtl->SharedRecoveryDataFlags |= XLR_PROMOTE_IS_TRIGGERED;
SpinLockRelease(&XLogRecoveryCtl->info_lck);
/*
@@ -4512,7 +4540,8 @@ HotStandbyActive(void)
{
/* spinlock is essential on machines with weak memory ordering! */
SpinLockAcquire(&XLogRecoveryCtl->info_lck);
- LocalHotStandbyActive = XLogRecoveryCtl->SharedHotStandbyActive;
+ LocalHotStandbyActive =
+ (XLogRecoveryCtl->SharedRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE) != 0;
SpinLockRelease(&XLogRecoveryCtl->info_lck);
return LocalHotStandbyActive;
@@ -4530,6 +4559,22 @@ HotStandbyActiveInReplay(void)
return LocalHotStandbyActive;
}
+/*
+ * It reports whether 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 XLR_STANDBY_MODE_REQUESTED flag
+ * can only be read after startup process is done.
+ */
+ return (XLogRecoveryCtl->SharedRecoveryDataFlags & XLR_STANDBY_MODE_REQUESTED) != 0;
+}
+
/*
* Get latest redo apply position.
*
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index c423464e8bc..ad94dd79629 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -144,6 +144,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 134e3b22fd8..9bf97f81d61 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6519,6 +6519,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 9e2be31c38987b565fe2b41b5470659911cf2a10 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 684838cbab7..10a6fa8b905 100644
--- a/src/test/recovery/t/004_timeline_switch.pl
+++ b/src/test/recovery/t/004_timeline_switch.pl
@@ -16,6 +16,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);
@@ -30,6 +35,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");
@@ -48,6 +58,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(
@@ -56,6 +74,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