Hi!
Michael,
I have fixed the patches according to your comments.
merge both local variables into a single bits32 store?
This is done in v3-0001-Standby-mode-requested.patch
Then this could be used with a function that returns a
text[] array with all the states retrieved?
Placed this in the v3-0002-Text-array-sql-wrapper.patch
The refactoring pieces and the function pieces should be split, for
clarity.
Sure. I also added the third patch with some tests. Perhaps it would be
usefull.
Respectfully,
Mikhail Litsarev
Postgres Professional: https://postgrespro.com
From cd32de9ec14dba0011f8c05e6a8ab7e4f7be045c Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsa...@postgrespro.ru>
Date: Thu, 13 Jun 2024 18:47:47 +0300
Subject: [PATCH] Tests for standby-is-requested mode.
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 | 25 ++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl
index 684838cbab7..11f0b22ae90 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_get_recovery_flags() for master
+my $ret_mode_primary = $node_primary->safe_psql('postgres',
+ 'SELECT pg_get_recovery_flags()');
+is($ret_mode_primary, '{}', "master is not a replica");
+
# Take backup
my $backup_name = 'my_backup';
$node_primary->backup($backup_name);
@@ -30,6 +35,12 @@ $node_standby_2->init_from_backup($node_primary, $backup_name,
has_streaming => 1);
$node_standby_2->start;
+# Validate standby-mode-requested state for replica node
+my $ret_mode_standby_1 = $node_standby_1->safe_psql('postgres',
+ 'SELECT pg_get_recovery_flags()');
+is($ret_mode_standby_1, '{STANDBY_MODE_REQUESTED}', "node_standby_1 is a replica");
+print($ret_mode_standby_1);
+
# Create some content on primary
$node_primary->safe_psql('postgres',
"CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a");
@@ -48,6 +59,15 @@ $node_standby_1->psql(
stdout => \$psql_out);
is($psql_out, 't', "promotion of standby with pg_promote");
+# Validate pg_get_recovery_flags() for master promoted from standby node.
+# STANDBY_MODE_REQUESTED is returned because standby.signal file
+# was found while being a replica.
+# Use it with pg_is_in_recovery() (will return false), for such use-cases.
+my $ret_mode_1 = $node_standby_1->safe_psql('postgres',
+ 'SELECT pg_get_recovery_flags()');
+is($ret_mode_1, '{PROMOTE_IS_TRIGGERED,STANDBY_MODE_REQUESTED}',
+ "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 +76,11 @@ primary_conninfo='$connstr_1'
));
$node_standby_2->restart;
+# Validate STANDBY_MODE_REQUESTED for second replica after restart
+my $ret_mode_standby_2 = $node_standby_2->safe_psql('postgres',
+ 'SELECT pg_get_recovery_flags()');
+is($ret_mode_standby_2, '{STANDBY_MODE_REQUESTED}', "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
From ac304f75d5f6f9bb9712ef4113da035ad74b7344 Mon Sep 17 00:00:00 2001
From: Mikhail Litsarev <m.litsa...@postgrespro.ru>
Date: Thu, 13 Jun 2024 18:37:13 +0300
Subject: [PATCH] Wrapper function to extract whole text array from the
SharedRecoveryDataFlags.
---
src/backend/access/transam/xlogfuncs.c | 31 +++++++++++++++++++++++
src/backend/access/transam/xlogrecovery.c | 17 +++++++++++++
src/include/access/xlogrecovery.h | 1 +
src/include/catalog/pg_proc.dat | 4 +++
4 files changed, 53 insertions(+)
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 4e46baaebdf..9e752639aa4 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -29,6 +29,7 @@
#include "replication/walreceiver.h"
#include "storage/fd.h"
#include "storage/standby.h"
+#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/pg_lsn.h"
@@ -747,3 +748,33 @@ pg_promote(PG_FUNCTION_ARGS)
wait_seconds)));
PG_RETURN_BOOL(false);
}
+
+Datum
+pg_get_recovery_flags(PG_FUNCTION_ARGS)
+{
+/*
+ * Currently supported number of recovery flags is equal to 2:
+ * {XLR_PROMOTE_IS_TRIGGERED, XLR_STANDBY_MODE_REQUESTED}.
+ * It can be extended in future.
+ */
+#define MAX_RECOVERY_FLAGS 2
+
+ bits32 recovery_flags;
+ int cnt = 0;
+ Datum flags[MAX_RECOVERY_FLAGS];
+ ArrayType *txt_arr;
+
+ recovery_flags = GetXLogRecoveryFlags();
+
+ if (recovery_flags & XLR_PROMOTE_IS_TRIGGERED)
+ flags[cnt++] = CStringGetTextDatum("PROMOTE_IS_TRIGGERED");
+
+ if (recovery_flags & XLR_STANDBY_MODE_REQUESTED)
+ flags[cnt++] = CStringGetTextDatum("STANDBY_MODE_REQUESTED");
+
+ Assert(cnt <= MAX_RECOVERY_FLAGS);
+
+ /* Returns bit array as Datum */
+ txt_arr = construct_array_builtin(flags, cnt, TEXTOID);
+ PG_RETURN_ARRAYTYPE_P(txt_arr);
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index de92f48e4ae..bd919732a56 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4399,6 +4399,22 @@ StartupRequestWalReceiverRestart(void)
}
}
+/*
+ * Return flags for recovery states.
+ *
+ * Please, see description of
+ * XLR_PROMOTE_IS_TRIGGERED and XLR_STANDBY_MODE_REQUESTED.
+ */
+bits32 GetXLogRecoveryFlags(void)
+{
+ bits32 flags;
+
+ SpinLockAcquire(&XLogRecoveryCtl->info_lck);
+ flags = XLogRecoveryCtl->SharedRecoveryDataFlags;
+ SpinLockRelease(&XLogRecoveryCtl->info_lck);
+
+ return flags;
+}
/*
* Has a standby promotion already been triggered?
@@ -4551,6 +4567,7 @@ HotStandbyActiveInReplay(void)
return (LocalRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE);
}
+
/*
* Get latest redo apply position.
*
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 2cb56fc365f..40487de1846 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -165,6 +165,7 @@ extern TimestampTz GetLatestXTime(void);
extern TimestampTz GetCurrentChunkReplayStartTime(void);
extern XLogRecPtr GetCurrentReplayRecPtr(TimeLineID *replayEndTLI);
+extern bits32 GetXLogRecoveryFlags(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 6a5476d3c4c..7b199780c01 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6520,6 +6520,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 => 'return flags for recovery states',
+ proname => 'pg_get_recovery_flags', provolatile => 'v', prorettype => '_text',
+ proargtypes => '', prosrc => 'pg_get_recovery_flags' },
{ oid => '3820', descr => 'current wal flush location',
proname => 'pg_last_wal_receive_lsn', provolatile => 'v',
--
2.34.1
From 23e23dd4c0b1c4c69021a4f2bf95586f39d8bd1f 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] Replace recovery boolean flags with a bits32 set.
Replace local and shared-memory flags (Local/Shared)HotStandbyActive,
(Local/Shared)PromoteIsTriggered with corresponding
(Local/Shared)RecoveryDataFlags.
Introduce XLR_STANDBY_MODE_REQUESTED state which indicates
that a node is in a standby mode at start, while recovery mode is on
(standby.signal file was found at start up).
---
src/backend/access/transam/xlogrecovery.c | 89 ++++++++++++++---------
src/include/access/xlogrecovery.h | 21 ++++++
2 files changed, 76 insertions(+), 34 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index b45b8331720..de92f48e4ae 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -170,16 +170,22 @@ static XLogRecPtr RedoStartLSN = InvalidXLogRecPtr;
static TimeLineID RedoStartTLI = 0;
/*
- * Local copy of SharedHotStandbyActive variable. False actually means "not
- * known, need to check the shared state".
- */
-static bool LocalHotStandbyActive = false;
-
-/*
- * Local copy of SharedPromoteIsTriggered variable. False actually means "not
- * known, need to check the shared state".
+ * This bit array is introduced to keep the local copies of RecoveryDataFlags
+ * in corresponding SharedRecoveryDataFlags bitset.
+ *
+ * Currently it provides the following states:
+ *
+ * -- Local copy of XLR_HOT_STANDBY_ACTIVE flag. False actually means "not
+ * known, need to check the shared state.
+ *
+ * -- Local copy of XLR_PROMOTE_IS_TRIGGERED flag. False actually means "not
+ * known, need to check the shared state".
+ *
+ * -- XLR_STANDBY_MODE_REQUESTED is not mirrored here.
+ *
+ * This bitset can be extended in future.
*/
-static bool LocalPromoteIsTriggered = false;
+static bits32 LocalRecoveryDataFlags;
/* Has the recovery code requested a walreceiver wakeup? */
static bool doRequestWalReceiverReply;
@@ -297,23 +303,26 @@ bool reachedConsistency = false;
static char *replay_image_masked = NULL;
static char *primary_image_masked = NULL;
-
/*
* 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;
+ bits32 SharedRecoveryDataFlags;
/*
* recoveryWakeupLatch is used to wake up the startup process to continue
@@ -1082,10 +1091,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)
{
@@ -2254,15 +2270,15 @@ CheckRecoveryConsistency(void)
* enabling connections.
*/
if (standbyState == STANDBY_SNAPSHOT_READY &&
- !LocalHotStandbyActive &&
+ !(LocalRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE) &&
reachedConsistency &&
IsUnderPostmaster)
{
SpinLockAcquire(&XLogRecoveryCtl->info_lck);
- XLogRecoveryCtl->SharedHotStandbyActive = true;
+ XLogRecoveryCtl->SharedRecoveryDataFlags |= XLR_HOT_STANDBY_ACTIVE;
SpinLockRelease(&XLogRecoveryCtl->info_lck);
- LocalHotStandbyActive = true;
+ LocalRecoveryDataFlags |= XLR_HOT_STANDBY_ACTIVE;
SendPostmasterSignal(PMSIGNAL_BEGIN_HOT_STANDBY);
}
@@ -2925,11 +2941,11 @@ static void
recoveryPausesHere(bool endOfRecovery)
{
/* Don't pause unless users can connect! */
- if (!LocalHotStandbyActive)
+ if (!(LocalRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE))
return;
/* Don't pause after standby promotion has been triggered */
- if (LocalPromoteIsTriggered)
+ if (LocalRecoveryDataFlags & XLR_PROMOTE_IS_TRIGGERED)
return;
if (endOfRecovery)
@@ -4398,21 +4414,23 @@ PromoteIsTriggered(void)
* triggered. We can't trigger a promotion again, so there's no need to
* keep checking after the shared variable has once been seen true.
*/
- if (LocalPromoteIsTriggered)
+ if (LocalRecoveryDataFlags & XLR_PROMOTE_IS_TRIGGERED)
return true;
SpinLockAcquire(&XLogRecoveryCtl->info_lck);
- LocalPromoteIsTriggered = XLogRecoveryCtl->SharedPromoteIsTriggered;
+ LocalRecoveryDataFlags &= ~XLR_PROMOTE_IS_TRIGGERED;
+ LocalRecoveryDataFlags |=
+ (XLogRecoveryCtl->SharedRecoveryDataFlags & XLR_PROMOTE_IS_TRIGGERED);
SpinLockRelease(&XLogRecoveryCtl->info_lck);
- return LocalPromoteIsTriggered;
+ return (LocalRecoveryDataFlags & XLR_PROMOTE_IS_TRIGGERED);
}
static void
SetPromoteIsTriggered(void)
{
SpinLockAcquire(&XLogRecoveryCtl->info_lck);
- XLogRecoveryCtl->SharedPromoteIsTriggered = true;
+ XLogRecoveryCtl->SharedRecoveryDataFlags |= XLR_PROMOTE_IS_TRIGGERED;
SpinLockRelease(&XLogRecoveryCtl->info_lck);
/*
@@ -4423,7 +4441,7 @@ SetPromoteIsTriggered(void)
*/
SetRecoveryPause(false);
- LocalPromoteIsTriggered = true;
+ LocalRecoveryDataFlags |= XLR_PROMOTE_IS_TRIGGERED;
}
/*
@@ -4432,7 +4450,7 @@ SetPromoteIsTriggered(void)
static bool
CheckForStandbyTrigger(void)
{
- if (LocalPromoteIsTriggered)
+ if (LocalRecoveryDataFlags & XLR_PROMOTE_IS_TRIGGERED)
return true;
if (IsPromoteSignaled() && CheckPromoteSignal())
@@ -4506,16 +4524,18 @@ HotStandbyActive(void)
* can't de-activate Hot Standby, so there's no need to keep checking
* after the shared variable has once been seen true.
*/
- if (LocalHotStandbyActive)
+ if (LocalRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE)
return true;
else
{
/* spinlock is essential on machines with weak memory ordering! */
SpinLockAcquire(&XLogRecoveryCtl->info_lck);
- LocalHotStandbyActive = XLogRecoveryCtl->SharedHotStandbyActive;
+ LocalRecoveryDataFlags &= ~XLR_HOT_STANDBY_ACTIVE;
+ LocalRecoveryDataFlags |=
+ (XLogRecoveryCtl->SharedRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE);
SpinLockRelease(&XLogRecoveryCtl->info_lck);
- return LocalHotStandbyActive;
+ return LocalRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE;
}
}
@@ -4527,7 +4547,8 @@ static bool
HotStandbyActiveInReplay(void)
{
Assert(AmStartupProcess() || !IsPostmasterEnvironment);
- return LocalHotStandbyActive;
+
+ return (LocalRecoveryDataFlags & XLR_HOT_STANDBY_ACTIVE);
}
/*
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index c423464e8bc..2cb56fc365f 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -16,6 +16,27 @@
#include "lib/stringinfo.h"
#include "utils/timestamp.h"
+/*
+ * 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.
+ * It is introduced to distinguish a replica from a regular instance
+ * in a Point In Time Recovery (PITR) mode.
+ *
+ * It is selected if standby.signal file is found at startup process
+ * It is skipped otherwise.
+ */
+#define XLR_STANDBY_MODE_REQUESTED 0x04
+
/*
* Recovery target type.
* Only set during a Point in Time recovery, not when in standby mode.
--
2.34.1