v3-0003-PoC-Convert-wal_level-a-PGC_SIGHUP-parameter.patch

application/x-patch

Filename: v3-0003-PoC-Convert-wal_level-a-PGC_SIGHUP-parameter.patch
Type: application/x-patch
Part: 0
Message: Re: POC: enable logical decoding when wal_level = 'replica' without a server restart

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v3-0003
Subject: PoC: Convert wal_level a PGC_SIGHUP parameter.
File+
src/backend/access/rmgrdesc/xlogdesc.c 10 0
src/backend/access/transam/Makefile 1 0
src/backend/access/transam/meson.build 1 0
src/backend/access/transam/xlog.c 74 15
src/backend/access/transam/xlogfuncs.c 1 1
src/backend/access/transam/xloglevel.c 575 0
src/backend/access/transam/xlogrecovery.c 5 0
src/backend/commands/publicationcmds.c 1 1
src/backend/postmaster/checkpointer.c 6 0
src/backend/postmaster/pgarch.c 26 0
src/backend/postmaster/postmaster.c 9 11
src/backend/replication/logical/decode.c 12 0
src/backend/replication/logical/logical.c 1 1
src/backend/replication/logical/slotsync.c 5 5
src/backend/replication/slot.c 3 3
src/backend/replication/walsender.c 29 0
src/backend/storage/ipc/ipci.c 2 0
src/backend/storage/ipc/procsignal.c 4 0
src/backend/storage/ipc/standby.c 3 3
src/backend/utils/activity/wait_event_names.txt 3 0
src/backend/utils/init/postinit.c 3 0
src/backend/utils/misc/bgtask.c 4 11
src/backend/utils/misc/guc_tables.c 2 2
src/bin/pg_controldata/pg_controldata.c 4 0
src/include/access/xlog.h 50 5
src/include/catalog/pg_control.h 1 0
src/include/postmaster/pgarch.h 1 0
src/include/replication/walsender.h 1 0
src/include/storage/lwlocklist.h 1 0
src/include/storage/procsignal.h 2 0
src/include/utils/bgtask.h 2 2
src/include/utils/guc_hooks.h 2 0
From 9ab16e2442449ce9cf2ac3f2681db4e461b7dcd2 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Thu, 23 Jan 2025 16:36:37 -0800
Subject: [PATCH v3 3/3] PoC: Convert wal_level a PGC_SIGHUP parameter.

---
 src/backend/access/rmgrdesc/xlogdesc.c        |  10 +
 src/backend/access/transam/Makefile           |   1 +
 src/backend/access/transam/meson.build        |   1 +
 src/backend/access/transam/xlog.c             |  89 ++-
 src/backend/access/transam/xlogfuncs.c        |   2 +-
 src/backend/access/transam/xloglevel.c        | 575 ++++++++++++++++++
 src/backend/access/transam/xlogrecovery.c     |   5 +
 src/backend/commands/publicationcmds.c        |   2 +-
 src/backend/postmaster/checkpointer.c         |   6 +
 src/backend/postmaster/pgarch.c               |  26 +
 src/backend/postmaster/postmaster.c           |  20 +-
 src/backend/replication/logical/decode.c      |  12 +
 src/backend/replication/logical/logical.c     |   2 +-
 src/backend/replication/logical/slotsync.c    |  10 +-
 src/backend/replication/slot.c                |   6 +-
 src/backend/replication/walsender.c           |  29 +
 src/backend/storage/ipc/ipci.c                |   2 +
 src/backend/storage/ipc/procsignal.c          |   4 +
 src/backend/storage/ipc/standby.c             |   6 +-
 .../utils/activity/wait_event_names.txt       |   3 +
 src/backend/utils/init/postinit.c             |   3 +
 src/backend/utils/misc/bgtask.c               |  15 +-
 src/backend/utils/misc/guc_tables.c           |   4 +-
 src/bin/pg_controldata/pg_controldata.c       |   4 +
 src/include/access/xlog.h                     |  55 +-
 src/include/catalog/pg_control.h              |   1 +
 src/include/postmaster/pgarch.h               |   1 +
 src/include/replication/walsender.h           |   1 +
 src/include/storage/lwlocklist.h              |   1 +
 src/include/storage/procsignal.h              |   2 +
 src/include/utils/bgtask.h                    |   4 +-
 src/include/utils/guc_hooks.h                 |   2 +
 32 files changed, 844 insertions(+), 60 deletions(-)
 create mode 100644 src/backend/access/transam/xloglevel.c

diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
index 58040f28656..742a4c8b34e 100644
--- a/src/backend/access/rmgrdesc/xlogdesc.c
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -164,6 +164,13 @@ xlog_desc(StringInfo buf, XLogReaderState *record)
 	{
 		int			wal_level;
 
+		memcpy(&wal_level, rec, sizeof(int));
+		appendStringInfo(buf, "wal_level %s", get_wal_level_string(wal_level));
+	}
+	else if (info == XLOG_WAL_LEVEL_CHANGE)
+	{
+		int			wal_level;
+
 		memcpy(&wal_level, rec, sizeof(int));
 		appendStringInfo(buf, "wal_level %s", get_wal_level_string(wal_level));
 	}
@@ -218,6 +225,9 @@ xlog_identify(uint8 info)
 		case XLOG_CHECKPOINT_REDO:
 			id = "CHECKPOINT_REDO";
 			break;
+		case XLOG_WAL_LEVEL_CHANGE:
+			id = "WAL_LEVEL_CHANGE";
+			break;
 	}
 
 	return id;
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db7..d2730952a6e 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -32,6 +32,7 @@ OBJS = \
 	xlogbackup.o \
 	xlogfuncs.o \
 	xloginsert.o \
+	xloglevel.o \
 	xlogprefetcher.o \
 	xlogreader.o \
 	xlogrecovery.o \
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index e8ae9b13c8e..af516cfd4a4 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -20,6 +20,7 @@ backend_sources += files(
   'xlogbackup.c',
   'xlogfuncs.c',
   'xloginsert.c',
+  'xloglevel.c',
   'xlogprefetcher.c',
   'xlogrecovery.c',
   'xlogstats.c',
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index a50fd99d9e5..00402dc02a0 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6147,6 +6147,18 @@ StartupXLOG(void)
 	Insert->fullPageWrites = lastFullPageWrites;
 	UpdateFullPageWrites();
 
+	/*
+	 * Update wal_level in shared memory.
+	 *
+	 * We use the wal_level value configured in this server rather than the
+	 * value in the ControlFile. We don't write a XLOG_WAL_LEVEL_CHANGE record
+	 * as the recovery still in-progress, but that change would be included in
+	 * the XLOG_PARAMETER_CHANGE record written by the subsequent
+	 * XLogReportParameters().
+	 */
+	if (performedWalRecovery)
+		UpdateWalLevelAfterRecovery(ControlFile->wal_level);
+
 	/*
 	 * Emit checkpoint or end-of-recovery record in XLOG, if required.
 	 */
@@ -7022,7 +7034,7 @@ CreateCheckPoint(int flags)
 	WALInsertLockAcquireExclusive();
 
 	checkPoint.fullPageWrites = Insert->fullPageWrites;
-	checkPoint.wal_level = wal_level;
+	checkPoint.wal_level = GetActiveWalLevel();
 
 	if (shutdown)
 	{
@@ -7076,9 +7088,11 @@ CreateCheckPoint(int flags)
 	 */
 	if (!shutdown)
 	{
+		int			level = GetActiveWalLevel();
+
 		/* Include WAL level in record for WAL summarizer's benefit. */
 		XLogBeginInsert();
-		XLogRegisterData(&wal_level, sizeof(wal_level));
+		XLogRegisterData(&level, sizeof(level));
 		(void) XLogInsert(RM_XLOG_ID, XLOG_CHECKPOINT_REDO);
 
 		/*
@@ -7403,7 +7417,7 @@ CreateEndOfRecoveryRecord(void)
 		elog(ERROR, "can only be used to end recovery");
 
 	xlrec.end_time = GetCurrentTimestamp();
-	xlrec.wal_level = wal_level;
+	xlrec.wal_level = GetActiveWalLevel();
 
 	WALInsertLockAcquireExclusive();
 	xlrec.ThisTimeLineID = XLogCtl->InsertTimeLineID;
@@ -8532,7 +8546,7 @@ xlog_redo(XLogReaderState *record)
 		 */
 		if (InRecovery && InHotStandby &&
 			xlrec.wal_level < WAL_LEVEL_LOGICAL &&
-			wal_level >= WAL_LEVEL_LOGICAL)
+			GetActiveWalLevel() >= WAL_LEVEL_LOGICAL)
 			InvalidateObsoleteReplicationSlots(RS_INVAL_WAL_LEVEL,
 											   0, InvalidOid,
 											   InvalidTransactionId);
@@ -8604,6 +8618,25 @@ xlog_redo(XLogReaderState *record)
 	{
 		/* nothing to do here, just for informational purposes */
 	}
+	else if (info == XLOG_WAL_LEVEL_CHANGE)
+	{
+		WalLevel	new_wal_level;
+
+		memcpy(&new_wal_level, XLogRecGetData(record), sizeof(new_wal_level));
+
+		if (InRecovery && InHotStandby &&
+			new_wal_level < WAL_LEVEL_LOGICAL &&
+			GetActiveWalLevel() >= WAL_LEVEL_LOGICAL)
+			InvalidateObsoleteReplicationSlots(RS_INVAL_WAL_LEVEL,
+											   0, InvalidOid,
+											   InvalidTransactionId);
+
+		/* Update our copy of the wal_level parameter in pg_control */
+		LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
+		ControlFile->wal_level = new_wal_level;
+		UpdateControlFile();
+		LWLockRelease(ControlFileLock);
+	}
 }
 
 /*
@@ -8825,11 +8858,11 @@ do_pg_backup_start(const char *backupidstr, bool fast, List **tablespaces,
 	 * During recovery, we don't need to check WAL level. Because, if WAL
 	 * level is not sufficient, it's impossible to get here during recovery.
 	 */
-	if (!backup_started_in_recovery && !XLogIsNeeded())
+	if (!backup_started_in_recovery && GetActiveWalLevel() < WAL_LEVEL_REPLICA)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("WAL level not sufficient for making an online backup"),
-				 errhint("\"wal_level\" must be set to \"replica\" or \"logical\" at server start.")));
+				 errhint("\"wal_level\" must be set to \"replica\" or \"logical\".")));
 
 	if (strlen(backupidstr) > MAXPGPATH)
 		ereport(ERROR,
@@ -9155,17 +9188,13 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 
 	Assert(state != NULL);
 
-	backup_stopped_in_recovery = RecoveryInProgress();
-
 	/*
-	 * During recovery, we don't need to check WAL level. Because, if WAL
-	 * level is not sufficient, it's impossible to get here during recovery.
+	 * This backend must have already called do_pg_backup_start(). The WAL
+	 * level check should have been done there.
 	 */
-	if (!backup_stopped_in_recovery && !XLogIsNeeded())
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("WAL level not sufficient for making an online backup"),
-				 errhint("\"wal_level\" must be set to \"replica\" or \"logical\" at server start.")));
+	Assert(get_backup_status() == SESSION_BACKUP_RUNNING);
+
+	backup_stopped_in_recovery = RecoveryInProgress();
 
 	/*
 	 * OK to update backup counter and session-level lock.
@@ -9452,6 +9481,36 @@ register_persistent_abort_backup_handler(void)
 	already_done = true;
 }
 
+/*
+ * Wait for all running backups to finish.
+ *
+ * Note that this function doesn't have any interlock to prevent new
+ * backups from being executed.
+ */
+void
+wait_for_backup_finish(void)
+{
+	for (;;)
+	{
+		int			running_backups;
+
+		CHECK_FOR_INTERRUPTS();
+
+		WALInsertLockAcquireExclusive();
+		running_backups = XLogCtl->Insert.runningBackups;
+		WALInsertLockRelease();
+
+		if (running_backups == 0)
+			break;
+
+		WaitLatch(MyLatch,
+				  WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+				  1000L,
+				  WAIT_EVENT_BACKUP_TERMINATION);
+		ResetLatch(MyLatch);
+	}
+}
+
 /*
  * Get latest WAL insert pointer
  */
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 8c3090165f0..82e4c43f185 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -242,7 +242,7 @@ pg_create_restore_point(PG_FUNCTION_ARGS)
 				 errmsg("recovery is in progress"),
 				 errhint("WAL control functions cannot be executed during recovery.")));
 
-	if (!XLogIsNeeded())
+	if (GetActiveWalLevel() < WAL_LEVEL_REPLICA)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("WAL level not sufficient for creating a restore point"),
diff --git a/src/backend/access/transam/xloglevel.c b/src/backend/access/transam/xloglevel.c
new file mode 100644
index 00000000000..1a9dfb10e0d
--- /dev/null
+++ b/src/backend/access/transam/xloglevel.c
@@ -0,0 +1,575 @@
+/*-------------------------------------------------------------------------
+ * xloglevel.c
+ *		Functionality for controlling 'wal_level' value online.
+ *
+ * This module implements dynamic WAL level changes via SIGHUP signal,
+ * eliminating the need for server restarts. The main idea is to decouple
+ * two aspects that were previously controlled by a single wal_level
+ * setting: the information included in WAL records and the functionalities
+ * available at each WAL level.
+ *
+ * To increase the WAL level, we first allow processes to write WAL records containing
+ * the additional information required by the target functionality, while keeping
+ * these functionalities unavailable. Once all processes have synchronized to
+ * generate the WAL records, the WAL level is increased further to enable
+ * the new functionalities.
+ *
+ * Decreasing the WAL level follows a similar pattern but requires additional steps.
+ * First, any functionality that won't be supported at the lower level must be
+ * terminated. For instance, decreasing from 'replica' to 'logical' requires
+ * invalidating all logical replication slots. After termination, processes reduce
+ * their WAL information content, and once all processes are synchronized, the WAL
+ * level is decreased.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/access/transam/xloglevel.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/parallel.h"
+#include "access/xact.h"
+#include "access/xloginsert.h"
+#include "catalog/pg_control.h"
+#include "miscadmin.h"
+#include "postmaster/bgwriter.h"
+#include "postmaster/interrupt.h"
+#include "postmaster/pgarch.h"
+#include "replication/slot.h"
+#include "storage/ipc.h"
+#include "storage/lmgr.h"
+#include "storage/procsignal.h"
+#include "tcop/tcopprot.h"
+#include "utils/bgtask.h"
+#include "utils/guc_hooks.h"
+
+typedef struct WalLevelCtlData
+{
+	/*
+	 * current_wal_level is the value used by all backends to know the active
+	 * wal_level, which affects what information is logged in WAL records and
+	 * which WAL-related features such as replication, WAL archiving and
+	 * logical decoding can be used in the system.
+	 *
+	 * The process-local 'wal_level' value, which is updated when changing
+	 * wal_level GUC parameter, might not represent the up-to-date WAL level.
+	 * We need to use GetActiveWalLevel() to get the active WAL level.
+	 */
+	WalLevel	current_wal_level;
+
+	/*
+	 * Different WAL level if we're in the process of changing WAL level
+	 * online. Otherwise, same as current_wal_level.
+	 */
+	WalLevel	target_wal_level;
+}			WalLevelCtlData;
+static WalLevelCtlData * WalLevelCtl = NULL;
+
+typedef void (*wal_level_decrease_action_cb) (void);
+typedef void (*wal_level_increase_action_cb) (void);
+
+bool		WalLevelInitialized = false;
+
+/*
+ * Both doStandbyInfoLogging and doLogicalInfoLogging are backend-local
+ * caches.
+ *
+ * They can be used to determine if information required by hot standby
+ * and/or logical decoding need to be written in WAL records. This local
+ * cache is updated when (1) process startup and (2) processing the
+ * global barrier.
+ */
+static bool doStandbyInfoLogging = false;
+static bool doLogicalInfoLogging = false;
+
+static void update_wal_logging_info_local(void);
+static void update_wal_level(WalLevel new_wal_level, bool notify_all);
+static void write_wal_level_change(WalLevel new_wal_level);
+static void increase_to_replica_action(void);
+static void increase_wal_level(WalLevel next_level, WalLevel target_level,
+							   wal_level_increase_action_cb action_cb);
+static void decrease_to_replica_action(void);
+static void decrease_to_minimal_action(void);
+static void decrease_wal_level(WalLevel next_level, WalLevel target_level,
+							   wal_level_decrease_action_cb action_cb);
+static const char *get_wal_level_string(WalLevel level);
+
+Size
+WalLevelCtlShmemSize(void)
+{
+	return sizeof(WalLevelCtlData);
+}
+
+void
+WalLevelCtlShmemInit(void)
+{
+	bool		found;
+
+	WalLevelCtl = ShmemInitStruct("wal_level control",
+								  WalLevelCtlShmemSize(),
+								  &found);
+
+	if (!found)
+	{
+		WalLevelCtl->current_wal_level = WAL_LEVEL_REPLICA;
+		WalLevelCtl->target_wal_level = WAL_LEVEL_REPLICA;
+	}
+}
+
+/*
+ * Initialize the global wal_level. This function is called after processing
+ * the configuration at startup.
+ */
+void
+InitializeWalLevelCtl(void)
+{
+	Assert(!WalLevelInitialized);
+
+	WalLevelCtl->current_wal_level = wal_level;
+	WalLevelCtl->target_wal_level = wal_level;
+	WalLevelInitialized = true;
+}
+
+/*
+ * Update both doStandbyInfoLogging and doLogicalInfoLogging based on the
+ * current global WAL level value.
+ */
+static void
+update_wal_logging_info_local(void)
+{
+	LWLockAcquire(WalLevelControlLock, LW_SHARED);
+	doStandbyInfoLogging =
+		(WalLevelCtl->current_wal_level >= WAL_LEVEL_STANDBY_INFO_LOGGING);
+	doLogicalInfoLogging =
+		(WalLevelCtl->current_wal_level >= WAL_LEVEL_LOGICAL_INFO_LOGGING);
+	LWLockRelease(WalLevelControlLock);
+}
+
+/*
+ * Initialize process-local xlog info status. This must be called during the
+ * process startup time.
+ */
+void
+InitWalLoggingState(void)
+{
+	update_wal_logging_info_local();
+}
+
+/*
+ * This function is called when we are ordered to update the local state
+ * by a ProcSignalBarrier.
+ */
+bool
+ProcessBarrierUpdateWalLoggingState(void)
+{
+	update_wal_logging_info_local();
+	return true;
+}
+
+/*
+ * Return true if the logical info logging is enabled.
+ */
+bool
+LogicalInfoLoggingEnabled(void)
+{
+	return doLogicalInfoLogging;
+}
+
+/*
+ * Return true if the standby info logging is enabled.
+ */
+bool
+StandbyInfoLoggingEnabled(void)
+{
+	return doStandbyInfoLogging;
+}
+
+/*
+ * Return the active wal_level stored on the shared memory, WalLevelCtl.
+ */
+int
+GetActiveWalLevel(void)
+{
+	WalLevel	level;
+
+	LWLockAcquire(WalLevelControlLock, LW_SHARED);
+	level = WalLevelCtl->current_wal_level;
+	LWLockRelease(WalLevelControlLock);
+
+	return level;
+}
+
+/*
+ * Return true if the logical decoding is ready to use.
+ */
+bool
+LogicalDecodingEnabled(void)
+{
+	return GetActiveWalLevel() >= WAL_LEVEL_LOGICAL;
+}
+
+/*
+ * Update WAL level after the recovery if necessary.
+ *
+ * This function must be called ONCE at the end of the recovery.
+ */
+void
+UpdateWalLevelAfterRecovery(WalLevel level)
+{
+	if (wal_level == level)
+		return;
+
+	WalLevelCtl->current_wal_level = level;
+	UpdateWalLevel();
+
+	/* Order all processes to reflect the new WAL level */
+	WaitForProcSignalBarrier(EmitProcSignalBarrier(PROCSIGNAL_BARRIER_UPDATE_WAL_LOGGING_STATE));
+}
+
+void
+UpdateWalLevel(void)
+{
+	/*
+	 * During recovery, we don't need to any coordination for WAL level
+	 * changing with running processes as any writes are not permitted yet.
+	 */
+	if (RecoveryInProgress())
+	{
+		WalLevelCtl->current_wal_level = wal_level;
+		return;
+	}
+
+	LWLockAcquire(WalLevelControlLock, LW_EXCLUSIVE);
+
+	if (WalLevelCtl->current_wal_level == wal_level)
+	{
+		LWLockRelease(WalLevelControlLock);
+		return;
+	}
+
+	WalLevelCtl->target_wal_level = wal_level;
+	LWLockRelease(WalLevelControlLock);
+
+	/* Offload the wal_level change work to a bgtask worker */
+	BgTaskRequest(BGTASK_TYPE_WAL_LEVEL_CHANGE, InvalidOid);
+}
+
+/*
+ * Update the WAL level value on shared memory.
+ *
+ * If notify_all is true, we order all processes to update their local
+ * WAL-logging information cache using global signal barriers, and wait
+ * for the complete.
+ */
+static void
+update_wal_level(WalLevel new_wal_level, bool notify_all)
+{
+	LWLockAcquire(WalLevelControlLock, LW_EXCLUSIVE);
+	WalLevelCtl->current_wal_level = new_wal_level;
+	LWLockRelease(WalLevelControlLock);
+
+	if (notify_all)
+	{
+		WaitForProcSignalBarrier(
+								 EmitProcSignalBarrier(PROCSIGNAL_BARRIER_UPDATE_WAL_LOGGING_STATE));
+	}
+}
+
+/*
+ * Write XLOG_WAL_LEVEL_CHANGE record with the given WAL level.
+ */
+static void
+write_wal_level_change(WalLevel new_wal_level)
+{
+	XLogBeginInsert();
+	XLogRegisterData((char *) (&new_wal_level), sizeof(int));
+	XLogInsert(RM_XLOG_ID, XLOG_WAL_LEVEL_CHANGE);
+}
+
+/*
+ * Callback function for increasing WAL level to 'replica' from 'minimal'.
+ */
+static void
+increase_to_replica_action(void)
+{
+	/*
+	 * We create a checkpoint to increase wal_level from 'minimal' so that we
+	 * can restart from there in case of a server crash.
+	 */
+	RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_FORCE | CHECKPOINT_WAIT);
+}
+
+/*
+ * Function to increase WAL level from 'minimal' to 'replica' or from 'replica'
+ * to 'logical'.
+ *
+ * 'target_level' is the destination WAL level, which must be 'replica' or
+ * 'logical'. 'next_level' is the intermediate level for the transition, which
+ * must be STANDBY_INFO_LOGGING or LOGICAL_INFO_LOGGING.
+ */
+static void
+increase_wal_level(WalLevel next_level, WalLevel target_level,
+				   wal_level_increase_action_cb action_cb)
+
+{
+	Assert(next_level == WAL_LEVEL_STANDBY_INFO_LOGGING ||
+		   next_level == WAL_LEVEL_LOGICAL_INFO_LOGGING);
+	Assert(target_level == WAL_LEVEL_REPLICA ||
+		   target_level == WAL_LEVEL_LOGICAL);
+
+	/*
+	 * Increase to the 'next_level' so that all process can start to write WAL
+	 * records with information required by the 'target_level'. We order all
+	 * process to reflect this change.
+	 */
+	update_wal_level(next_level, true);
+
+	/* Invoke the callback function, if specified */
+	if (action_cb)
+		action_cb();
+
+	START_CRIT_SECTION();
+
+	/*
+	 * Now increase to the 'target_level'.
+	 *
+	 * It's safe to take increase the WAL level, even when not strictly
+	 * required. So first set it true and then write the WAL record.
+	 */
+	update_wal_level(target_level, false);
+	write_wal_level_change(target_level);
+
+	END_CRIT_SECTION();
+
+	ereport(DEBUG1,
+			(errmsg("wal_level has been increased to \"%s\"",
+					get_wal_level_string(target_level))));
+}
+
+/*
+ * Callback function for decreasing WAL level to 'logical' from 'replica'.
+ */
+static void
+decrease_to_replica_action(void)
+{
+	/*
+	 * Invalidate all logical replication slots, terminating processes doing
+	 * logical decoding (and logical replication) too.
+	 */
+	InvalidateObsoleteReplicationSlots(RS_INVAL_WAL_LEVEL,
+									   0, InvalidOid,
+									   InvalidTransactionId);
+}
+
+/*
+ * Callback function for decreasing WAL level to 'replica' from 'minimal'.
+ *
+ * We terminate some functionalities that are not available at 'minimal' such
+ * as WAL archival and log shipping. The WAL level has already been decreased
+ * to WAL_LEVEL_STANDBY_INFO_LOGGING, we don't need to deal with the case
+ * where these aux processes are concurrently launched.
+ */
+static void
+decrease_to_minimal_action(void)
+{
+	/* shutdown archiver */
+	PgArchShutdown();
+
+	/* shutdown wal senders */
+	WalSndTerminate();
+	WalSndWaitStopping();
+
+	/* wait for currently running backups to finish */
+	wait_for_backup_finish();
+}
+
+/*
+ * Function to increase WAL level from 'logical' to 'replica' or from 'replica'
+ * to 'minimal'.
+ *
+ * 'target_level' is the destination WAL level, which must be 'replica' or
+ * 'minimal'. 'next_level' is the intermediate level for the transition, which
+ * must be STANDBY_INFO_LOGGING or LOGICAL_INFO_LOGGING.
+ */
+static void
+decrease_wal_level(WalLevel next_level, WalLevel target_level,
+				   wal_level_decrease_action_cb action_cb)
+{
+	Assert(next_level == WAL_LEVEL_STANDBY_INFO_LOGGING ||
+		   next_level == WAL_LEVEL_LOGICAL_INFO_LOGGING);
+	Assert(target_level == WAL_LEVEL_REPLICA ||
+		   target_level == WAL_LEVEL_MINIMAL);
+
+	/*
+	 * Decrease to the 'next_level' to prevent functionality that require the
+	 * current WAL level from being newly started while not affecting the
+	 * information contained in WAL records.
+	 */
+	update_wal_level(next_level, true);
+
+	/* Invoke the callback function, if specified */
+	if (action_cb)
+		action_cb();
+
+	START_CRIT_SECTION();
+
+	/*
+	 * Now increase to the 'target_level'.
+	 *
+	 * We must not decreasing WAL level before writing the WAL records,
+	 * because otherwise we would end up having insufficient information in
+	 * WAL records. Therefore, unlike increasing the WAL level, we write the
+	 * WAL recordand then set the shared WAL level.
+	 */
+	write_wal_level_change(target_level);
+	update_wal_level(target_level, false);
+
+	END_CRIT_SECTION();
+
+	/* Order all processed to disable logical information WAL-logging */
+	WaitForProcSignalBarrier(EmitProcSignalBarrier(PROCSIGNAL_BARRIER_UPDATE_WAL_LOGGING_STATE));
+
+	ereport(DEBUG1,
+			(errmsg("wal_level has been decreased to \"%s\"",
+					get_wal_level_string(target_level))));
+
+}
+
+void
+BgTaskWalLevelChange(void)
+{
+	WalLevel	current;
+	WalLevel	target;
+
+	LWLockAcquire(WalLevelControlLock, LW_SHARED);
+	current = WalLevelCtl->current_wal_level;
+	target = WalLevelCtl->target_wal_level;
+	LWLockRelease(WalLevelControlLock);
+
+	/* The target WAL level must not be intermediate levels */
+	Assert(target == WAL_LEVEL_MINIMAL ||
+		   target == WAL_LEVEL_REPLICA ||
+		   target == WAL_LEVEL_LOGICAL);
+
+	/* No need to change wal_level, exit */
+	if (current == target)
+		return;
+
+	ereport(LOG,
+			(errmsg("changing wal_level from \"%s\" to \"%s\"",
+					get_wal_level_string(current), get_wal_level_string(target))));
+
+	if (target > current)
+	{
+		/* We're increasing the WAL level one by one */
+
+		if (current == WAL_LEVEL_MINIMAL ||
+			current == WAL_LEVEL_STANDBY_INFO_LOGGING)
+		{
+			/* increasing to 'replica' */
+			increase_wal_level(WAL_LEVEL_STANDBY_INFO_LOGGING,
+							   WAL_LEVEL_REPLICA,
+							   increase_to_replica_action);
+		}
+
+		if (target == WAL_LEVEL_LOGICAL)
+		{
+			/* increasing to 'logical' */
+			increase_wal_level(WAL_LEVEL_LOGICAL_INFO_LOGGING,
+							   WAL_LEVEL_LOGICAL,
+							   NULL);
+		}
+	}
+	else
+	{
+		/* We're decreasing the WAL level one by one */
+
+		if (current == WAL_LEVEL_LOGICAL)
+		{
+			/* decreasing to 'replica' */
+			decrease_wal_level(WAL_LEVEL_LOGICAL_INFO_LOGGING,
+							   WAL_LEVEL_REPLICA,
+							   decrease_to_replica_action);
+		}
+
+		if (target == WAL_LEVEL_MINIMAL)
+		{
+			/* decreasing to 'replica' */
+			decrease_wal_level(WAL_LEVEL_STANDBY_INFO_LOGGING,
+							   WAL_LEVEL_MINIMAL,
+							   decrease_to_minimal_action);
+		}
+	}
+
+	ereport(LOG,
+			(errmsg("successfully made wal_level \"%s\" effective",
+					get_wal_level_string(target))));
+}
+
+/*
+ * Find a string representation of wal_level.
+ *
+ * This function doesn't support deprecated wal_level values such
+ * as 'archive'.
+ */
+static const char *
+get_wal_level_string(WalLevel level)
+{
+	switch (level)
+	{
+		case WAL_LEVEL_MINIMAL:
+			return "minimal";
+		case WAL_LEVEL_STANDBY_INFO_LOGGING:
+			return "minimal-standby_info_logging";
+		case WAL_LEVEL_REPLICA:
+			return "replica";
+		case WAL_LEVEL_LOGICAL_INFO_LOGGING:
+			return "replica-logical_info_logging";
+		case WAL_LEVEL_LOGICAL:
+			return "logical";
+	}
+
+	return "???";
+}
+
+const char *
+show_wal_level(void)
+{
+	return get_wal_level_string(GetActiveWalLevel());
+}
+
+bool
+check_wal_level(int *newval, void **extra, GucSource source)
+{
+	/* Just accept the value when restoring state in a parallel worker */
+	if (InitializingParallelWorker)
+		return true;
+
+	if (!WalLevelInitialized)
+		return true;
+
+	if (wal_level != *newval)
+	{
+		if (RecoveryInProgress())
+		{
+			GUC_check_errmsg("cannot change \"wal_level\" during recovery");
+			return false;
+		}
+
+		if (WalLevelCtl->current_wal_level != WalLevelCtl->target_wal_level)
+		{
+			GUC_check_errmsg("cannot change \"wal_level\" while changing level is in-progress");
+			GUC_check_errdetail("\"wal_level\" is being changed to \"%s\"",
+								get_wal_level_string(WalLevelCtl->target_wal_level));
+			return false;
+		}
+	}
+
+	return true;
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 473de6710d7..bc189570141 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1122,6 +1122,11 @@ validateRecoveryParameters(void)
 			ereport(WARNING,
 					(errmsg("specified neither \"primary_conninfo\" nor \"restore_command\""),
 					 errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
+
+		if (wal_level == WAL_LEVEL_MINIMAL)
+			ereport(FATAL,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot enter standby mode with \"wal_level=minimal\"")));
 	}
 	else
 	{
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 150a768d16f..df927420a4b 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -921,7 +921,7 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
 
 	InvokeObjectPostCreateHook(PublicationRelationId, puboid, 0);
 
-	if (wal_level != WAL_LEVEL_LOGICAL)
+	if (!XLogLogicalInfoActive())
 		ereport(WARNING,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("\"wal_level\" is insufficient to publish logical changes"),
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index b94f9cdff21..6da71f49b07 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -1377,6 +1377,12 @@ UpdateSharedMemoryConfig(void)
 	 */
 	UpdateFullPageWrites();
 
+	/*
+	 * If wal_level has been changed by SIGHUP, delegate changing wal_level
+	 * value to the wal level control worker.
+	 */
+	UpdateWalLevel();
+
 	elog(DEBUG2, "checkpointer updated shared memory configuration values");
 }
 
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 12ee815a626..d3001c6ebd3 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -291,6 +291,32 @@ PgArchWakeup(void)
 		SetLatch(&ProcGlobal->allProcs[arch_pgprocno].procLatch);
 }
 
+void
+PgArchShutdown(void)
+{
+	int			arch_pgprocno = PgArch->pgprocno;
+
+	if (arch_pgprocno == INVALID_PROC_NUMBER)
+		return;
+
+	/* terminate archiver */
+	kill(GetPGProcByNumber(arch_pgprocno)->pid, SIGUSR2);
+
+	/* and wait for it to exit */
+	for (;;)
+	{
+		CHECK_FOR_INTERRUPTS();
+
+		/* is it gone? */
+		if (arch_pgprocno == INVALID_PROC_NUMBER)
+			break;
+
+		WaitLatch(MyLatch,
+				  WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+				  100L, WAIT_EVENT_ARCHIVER_SHUTDOWN);
+		ResetLatch(MyLatch);
+	}
+}
 
 /* SIGUSR2 signal handler for archiver process */
 static void
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 108a1e5cfbe..3f9b907754f 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -835,15 +835,6 @@ PostmasterMain(int argc, char *argv[])
 					 MaxConnections);
 		ExitPostmaster(1);
 	}
-	if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)
-		ereport(ERROR,
-				(errmsg("WAL archival cannot be enabled when \"wal_level\" is \"minimal\"")));
-	if (max_wal_senders > 0 && wal_level == WAL_LEVEL_MINIMAL)
-		ereport(ERROR,
-				(errmsg("WAL streaming (\"max_wal_senders\" > 0) requires \"wal_level\" to be \"replica\" or \"logical\"")));
-	if (summarize_wal && wal_level == WAL_LEVEL_MINIMAL)
-		ereport(ERROR,
-				(errmsg("WAL cannot be summarized when \"wal_level\" is \"minimal\"")));
 
 	/*
 	 * Other one-time internal sanity checks can go here, if they are fast.
@@ -1036,6 +1027,12 @@ PostmasterMain(int argc, char *argv[])
 	RemovePgTempFilesInDir(PG_TEMP_FILES_DIR, true, false);
 #endif
 
+	/*
+	 * Initialize logical info logging and logical decoding state. We must do
+	 * this after initializing GUCs as it reflects wal_level setting.
+	 */
+	InitializeWalLevelCtl();
+
 	/*
 	 * Forcibly remove the files signaling a standby promotion request.
 	 * Otherwise, the existence of those files triggers a promotion too early,
@@ -3285,7 +3282,7 @@ LaunchMissingBackgroundProcesses(void)
 	 * If WAL archiving is enabled always, we are allowed to start archiver
 	 * even during recovery.
 	 */
-	if (PgArchPMChild == NULL &&
+	if (GetActiveWalLevel() >= WAL_LEVEL_REPLICA && PgArchPMChild == NULL &&
 		((XLogArchivingActive() && pmState == PM_RUN) ||
 		 (XLogArchivingAlways() && (pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY))) &&
 		PgArchCanRestart())
@@ -3331,7 +3328,8 @@ LaunchMissingBackgroundProcesses(void)
 	}
 
 	/* If we need to start a WAL summarizer, try to do that now */
-	if (summarize_wal && WalSummarizerPMChild == NULL &&
+	if (summarize_wal && GetActiveWalLevel() >= WAL_LEVEL_REPLICA &&
+		WalSummarizerPMChild == NULL &&
 		(pmState == PM_RUN || pmState == PM_HOT_STANDBY) &&
 		Shutdown <= SmartShutdown)
 		WalSummarizerPMChild = StartChildProcess(B_WAL_SUMMARIZER);
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 24d88f368d8..24782bd6fbd 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -178,6 +178,18 @@ xlog_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
 				}
 				break;
 			}
+		case XLOG_WAL_LEVEL_CHANGE:
+			{
+				int			new_wal_level;
+
+				memcpy(&new_wal_level, XLogRecGetData(buf->record),
+					   sizeof(new_wal_level));
+
+				if (new_wal_level < WAL_LEVEL_LOGICAL)
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("logical decoding on standby requires \"wal_level\" >= \"logical\" on the primary")));
+			}
 		case XLOG_NOOP:
 		case XLOG_NEXTOID:
 		case XLOG_SWITCH:
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8ea846bfc3b..8679b2d0ef7 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -115,7 +115,7 @@ CheckLogicalDecodingRequirements(void)
 	 * needs the same check.
 	 */
 
-	if (wal_level < WAL_LEVEL_LOGICAL)
+	if (!LogicalDecodingEnabled())
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("logical decoding requires \"wal_level\" >= \"logical\"")));
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 987857b9491..3cae6ab7d41 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -1038,14 +1038,14 @@ ValidateSlotSyncParams(int elevel)
 {
 	/*
 	 * Logical slot sync/creation requires wal_level >= logical.
-	 *
-	 * Since altering the wal_level requires a server restart, so error out in
-	 * this case regardless of elevel provided by caller.
 	 */
-	if (wal_level < WAL_LEVEL_LOGICAL)
-		ereport(ERROR,
+	if (!LogicalDecodingEnabled())
+	{
+		ereport(elevel,
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("replication slot synchronization requires \"wal_level\" >= \"logical\""));
+		return false;
+	}
 
 	/*
 	 * A physical replication slot(primary_slot_name) is required on the
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fe5acd8b1fc..b092008ce56 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1403,7 +1403,7 @@ CheckSlotRequirements(void)
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"max_replication_slots\" > 0")));
 
-	if (wal_level < WAL_LEVEL_REPLICA)
+	if (GetActiveWalLevel() < WAL_LEVEL_REPLICA)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("replication slots can only be used if \"wal_level\" >= \"replica\"")));
@@ -2360,13 +2360,13 @@ RestoreSlotFromDisk(const char *name)
 	 * NB: Changing the requirements here also requires adapting
 	 * CheckSlotRequirements() and CheckLogicalDecodingRequirements().
 	 */
-	if (cp.slotdata.database != InvalidOid && wal_level < WAL_LEVEL_LOGICAL)
+	if (cp.slotdata.database != InvalidOid && GetActiveWalLevel() < WAL_LEVEL_LOGICAL)
 		ereport(FATAL,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("logical replication slot \"%s\" exists, but \"wal_level\" < \"logical\"",
 						NameStr(cp.slotdata.name)),
 				 errhint("Change \"wal_level\" to be \"logical\" or higher.")));
-	else if (wal_level < WAL_LEVEL_REPLICA)
+	else if (GetActiveWalLevel() < WAL_LEVEL_REPLICA)
 		ereport(FATAL,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("physical replication slot \"%s\" exists, but \"wal_level\" < \"replica\"",
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 446d10c1a7d..9d0b33cf833 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -277,6 +277,11 @@ static void WalSndSegmentOpen(XLogReaderState *state, XLogSegNo nextSegNo,
 void
 InitWalSender(void)
 {
+	if (GetActiveWalLevel() < WAL_LEVEL_REPLICA)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("WAL senders require \"wal_level\" to be \"replica\" or \"logical\"")));
+
 	am_cascading_walsender = RecoveryInProgress();
 
 	/* Create a per-walsender data structure in shared memory */
@@ -3733,6 +3738,30 @@ WalSndInitStopping(void)
 	}
 }
 
+/*
+ * Terminate all running walsenders. Unlike shutting down them using
+ * WalSndInitStopping, this function can be used to terminate walsenders
+ * even while normal backends are generating WAL records.
+ */
+void
+WalSndTerminate(void)
+{
+	for (int i = 0; i < max_wal_senders; i++)
+	{
+		WalSnd	   *walsnd = &WalSndCtl->walsnds[i];
+		pid_t		pid;
+
+		SpinLockAcquire(&walsnd->mutex);
+		pid = walsnd->pid;
+		SpinLockRelease(&walsnd->mutex);
+
+		if (pid == 0)
+			continue;
+
+		kill(pid, SIGUSR2);
+	}
+}
+
 /*
  * Wait that all the WAL senders have quit or reached the stopping state. This
  * is used by the checkpointer to control when the shutdown checkpoint can
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 7c3c74dcdaf..2e5f990afe3 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -149,6 +149,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, InjectionPointShmemSize());
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, BgTaskShmemSize());
+	size = add_size(size, WalLevelCtlShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -331,6 +332,7 @@ CreateOrAttachShmemStructs(void)
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
 	SlotSyncShmemInit();
+	WalLevelCtlShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 7401b6e625e..b930036d3a3 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -18,6 +18,7 @@
 #include <unistd.h>
 
 #include "access/parallel.h"
+#include "access/xlog.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -573,6 +574,9 @@ ProcessProcSignalBarrier(void)
 					case PROCSIGNAL_BARRIER_SMGRRELEASE:
 						processed = ProcessBarrierSmgrRelease();
 						break;
+					case PROCSIGNAL_BARRIER_UPDATE_WAL_LOGGING_STATE:
+						processed = ProcessBarrierUpdateWalLoggingState();
+						break;
 				}
 
 				/*
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 5acb4508f85..761f301cf50 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -498,7 +498,7 @@ ResolveRecoveryConflictWithSnapshot(TransactionId snapshotConflictHorizon,
 	 * seems OK, given that this kind of conflict should not normally be
 	 * reached, e.g. due to using a physical replication slot.
 	 */
-	if (wal_level >= WAL_LEVEL_LOGICAL && isCatalogRel)
+	if (GetActiveWalLevel() >= WAL_LEVEL_LOGICAL && isCatalogRel)
 		InvalidateObsoleteReplicationSlots(RS_INVAL_HORIZON, 0, locator.dbOid,
 										   snapshotConflictHorizon);
 }
@@ -1313,13 +1313,13 @@ LogStandbySnapshot(void)
 	 * record. Fortunately this routine isn't executed frequently, and it's
 	 * only a shared lock.
 	 */
-	if (wal_level < WAL_LEVEL_LOGICAL)
+	if (!XLogLogicalInfoActive())
 		LWLockRelease(ProcArrayLock);
 
 	recptr = LogCurrentRunningXacts(running);
 
 	/* Release lock if we kept it longer ... */
-	if (wal_level >= WAL_LEVEL_LOGICAL)
+	if (XLogLogicalInfoActive())
 		LWLockRelease(ProcArrayLock);
 
 	/* GetRunningTransactionData() acquired XidGenLock, we must release it */
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index fef6887eb43..6bdf585f1e5 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -52,6 +52,7 @@
 Section: ClassName - WaitEventActivity
 
 ARCHIVER_MAIN	"Waiting in main loop of archiver process."
+ARCHIVER_SHUTDOWN	"Waiting for archiver process to be terminated."
 AUTOVACUUM_MAIN	"Waiting in main loop of autovacuum launcher process."
 BGWRITER_HIBERNATE	"Waiting in background writer process, hibernating."
 BGWRITER_MAIN	"Waiting in main loop of background writer process."
@@ -106,6 +107,7 @@ APPEND_READY	"Waiting for subplan nodes of an <literal>Append</literal> plan nod
 ARCHIVE_CLEANUP_COMMAND	"Waiting for <xref linkend="guc-archive-cleanup-command"/> to complete."
 ARCHIVE_COMMAND	"Waiting for <xref linkend="guc-archive-command"/> to complete."
 BACKEND_TERMINATION	"Waiting for the termination of another backend."
+BACKUP_TERMINATION	"Waiting for all running backups to complete."
 BACKUP_WAIT_WAL_ARCHIVE	"Waiting for WAL files required for a backup to be successfully archived."
 BGWORKER_SHUTDOWN	"Waiting for background worker to shut down."
 BGWORKER_STARTUP	"Waiting for background worker to start up."
@@ -347,6 +349,7 @@ DSMRegistry	"Waiting to read or update the dynamic shared memory registry."
 InjectionPoint	"Waiting to read or update information related to injection points."
 SerialControl	"Waiting to read or update shared <filename>pg_serial</filename> state."
 BgTaskControl	"Waiting to read or update shared background task information."
+WalLevelControl	"Waiting to read or update wal_level control state."
 
 #
 # END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index d70a5be446a..97675d81c54 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -647,6 +647,9 @@ BaseInit(void)
 	/* Initialize lock manager's local structs */
 	InitLockManagerAccess();
 
+	/* Initialize WAL-logging state */
+	InitWalLoggingState();
+
 	/*
 	 * Initialize replication slots after pgstat. The exit hook might need to
 	 * drop ephemeral slots, which in turn triggers stats reporting.
diff --git a/src/backend/utils/misc/bgtask.c b/src/backend/utils/misc/bgtask.c
index ac67968b9a1..15645e65d1f 100644
--- a/src/backend/utils/misc/bgtask.c
+++ b/src/backend/utils/misc/bgtask.c
@@ -24,6 +24,7 @@
 
 #include "postgres.h"
 
+#include "access/xlog.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
 #include "miscadmin.h"
@@ -60,8 +61,6 @@ typedef struct BgTaskCtlData
 }			BgTaskCtlData;
 static BgTaskCtlData * BgTaskCtl = NULL;
 
-static void dummy_bgtask_fn(void);
-
 static const struct
 {
 	const char *name;
@@ -69,9 +68,9 @@ static const struct
 }			InternalBgTasks[] =
 
 {
-	[BGTASK_TYPE_DUMMY] = {
-		.name = "dummy task",
-		.func = &dummy_bgtask_fn,
+	[BGTASK_TYPE_WAL_LEVEL_CHANGE] = {
+		.name = "online wal_level change",
+		.func = &BgTaskWalLevelChange,
 	},
 };
 
@@ -233,9 +232,3 @@ BgTaskWorkerDetach(int code, Datum arg)
 	BgTaskCtl->task_slots[type].in_progress = false;
 	LWLockRelease(BgTaskControlLock);
 }
-
-static void
-dummy_bgtask_fn(void)
-{
-	elog(LOG, "executed dummy background task");
-}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 42728189322..e277c881593 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -5091,13 +5091,13 @@ struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"wal_level", PGC_POSTMASTER, WAL_SETTINGS,
+		{"wal_level", PGC_SIGHUP, WAL_SETTINGS,
 			gettext_noop("Sets the level of information written to the WAL."),
 			NULL
 		},
 		&wal_level,
 		WAL_LEVEL_REPLICA, wal_level_options,
-		NULL, NULL, NULL
+		check_wal_level, NULL, show_wal_level
 	},
 
 	{
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index cf11ab3f2ee..61a5593869d 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -76,8 +76,12 @@ wal_level_str(WalLevel wal_level)
 	{
 		case WAL_LEVEL_MINIMAL:
 			return "minimal";
+		case WAL_LEVEL_STANDBY_INFO_LOGGING:
+			return "minimal-standby_info_logging";
 		case WAL_LEVEL_REPLICA:
 			return "replica";
+		case WAL_LEVEL_LOGICAL_INFO_LOGGING:
+			return "replica-logical_info_logging";
 		case WAL_LEVEL_LOGICAL:
 			return "logical";
 	}
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 4411c1468ac..2788d20da5e 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -71,10 +71,38 @@ extern PGDLLIMPORT int XLogArchiveMode;
 /* WAL levels */
 typedef enum WalLevel
 {
+	/*
+	 * This WAL level corresponds to 'minimal', where we don't WAL-logging for
+	 * neither archival nor log-shipping.
+	 */
 	WAL_LEVEL_MINIMAL = 0,
+
+	/*
+	 * In this level, we enable WAL-logging information required only for
+	 * archival, log-shipping, and hot standby. However, these functionalities
+	 * are not available with this level.
+	 */
+	WAL_LEVEL_STANDBY_INFO_LOGGING,
+
+	/*
+	 * This WAL level corresponds to 'replica', where we allow archival, and,
+	 * log-shipping, and hot standby.
+	 */
 	WAL_LEVEL_REPLICA,
+
+	/*
+	 * In this level, we enable WAL-logging information required for logical
+	 * decoding. However, logical decoding is not available with this level.
+	 */
+	WAL_LEVEL_LOGICAL_INFO_LOGGING,
+
+	/*
+	 * This WAL level corresponds to 'logical', where we allow logical
+	 * decoding.
+	 */
 	WAL_LEVEL_LOGICAL,
 } WalLevel;
+extern bool WalLevelInitialized;
 
 /* Compression algorithms for WAL */
 typedef enum WalCompression
@@ -97,16 +125,16 @@ extern PGDLLIMPORT int wal_level;
 
 /* Is WAL archiving enabled (always or only while server is running normally)? */
 #define XLogArchivingActive() \
-	(AssertMacro(XLogArchiveMode == ARCHIVE_MODE_OFF || wal_level >= WAL_LEVEL_REPLICA), XLogArchiveMode > ARCHIVE_MODE_OFF)
+	(AssertMacro(XLogArchiveMode == ARCHIVE_MODE_OFF || GetActiveWalLevel() >= WAL_LEVEL_REPLICA), XLogArchiveMode > ARCHIVE_MODE_OFF)
 /* Is WAL archiving enabled always (even during recovery)? */
 #define XLogArchivingAlways() \
-	(AssertMacro(XLogArchiveMode == ARCHIVE_MODE_OFF || wal_level >= WAL_LEVEL_REPLICA), XLogArchiveMode == ARCHIVE_MODE_ALWAYS)
+	(AssertMacro(XLogArchiveMode == ARCHIVE_MODE_OFF || GetActiveWalLevel() >= WAL_LEVEL_REPLICA), XLogArchiveMode == ARCHIVE_MODE_ALWAYS)
 
 /*
  * Is WAL-logging necessary for archival or log-shipping, or can we skip
  * WAL-logging if we fsync() the data before committing instead?
  */
-#define XLogIsNeeded() (wal_level >= WAL_LEVEL_REPLICA)
+#define XLogIsNeeded() (StandbyInfoLoggingEnabled())
 
 /*
  * Is a full-page image needed for hint bit updates?
@@ -120,10 +148,10 @@ extern PGDLLIMPORT int wal_level;
 #define XLogHintBitIsNeeded() (DataChecksumsEnabled() || wal_log_hints)
 
 /* Do we need to WAL-log information required only for Hot Standby and logical replication? */
-#define XLogStandbyInfoActive() (wal_level >= WAL_LEVEL_REPLICA)
+#define XLogStandbyInfoActive() (StandbyInfoLoggingEnabled())
 
 /* Do we need to WAL-log information required only for logical replication? */
-#define XLogLogicalInfoActive() (wal_level >= WAL_LEVEL_LOGICAL)
+#define XLogLogicalInfoActive() (LogicalInfoLoggingEnabled())
 
 #ifdef WAL_DEBUG
 extern PGDLLIMPORT bool XLOG_DEBUG;
@@ -270,6 +298,22 @@ extern void SetInstallXLogFileSegmentActive(void);
 extern bool IsInstallXLogFileSegmentActive(void);
 extern void XLogShutdownWalRcv(void);
 
+/*
+ * Routines used by xloglevel.c to control WAL-logging information.
+ */
+extern Size WalLevelCtlShmemSize(void);
+extern void WalLevelCtlShmemInit(void);
+extern void InitializeWalLevelCtl(void);
+extern void InitWalLoggingState(void);
+extern bool ProcessBarrierUpdateWalLoggingState(void);
+extern bool LogicalInfoLoggingEnabled(void);
+extern bool StandbyInfoLoggingEnabled(void);
+extern bool LogicalDecodingEnabled(void);
+extern int	GetActiveWalLevel(void);
+extern void UpdateWalLevelAfterRecovery(WalLevel level);
+extern void UpdateWalLevel(void);
+extern void BgTaskWalLevelChange(void);
+
 /*
  * Routines to start, stop, and get status of a base backup.
  */
@@ -297,6 +341,7 @@ extern void do_pg_backup_stop(BackupState *state, bool waitforarchive);
 extern void do_pg_abort_backup(int code, Datum arg);
 extern void register_persistent_abort_backup_handler(void);
 extern SessionBackupState get_backup_status(void);
+extern void wait_for_backup_finish(void);
 
 /* File path names (all relative to $PGDATA) */
 #define RECOVERY_SIGNAL_FILE	"recovery.signal"
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index 3797f25b306..39a14c67f6a 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -80,6 +80,7 @@ typedef struct CheckPoint
 /* 0xC0 is used in Postgres 9.5-11 */
 #define XLOG_OVERWRITE_CONTRECORD		0xD0
 #define XLOG_CHECKPOINT_REDO			0xE0
+#define XLOG_WAL_LEVEL_CHANGE			0xF0
 
 
 /*
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index 8fc6bfeec1b..73f2d13223d 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -31,6 +31,7 @@ extern void PgArchShmemInit(void);
 extern bool PgArchCanRestart(void);
 extern void PgArchiverMain(char *startup_data, size_t startup_data_len) pg_attribute_noreturn();
 extern void PgArchWakeup(void);
+extern void PgArchShutdown(void);
 extern void PgArchForceDirScan(void);
 
 #endif							/* _PGARCH_H */
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index c3e8e191339..d6d426915e9 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -44,6 +44,7 @@ extern void WalSndSignals(void);
 extern Size WalSndShmemSize(void);
 extern void WalSndShmemInit(void);
 extern void WalSndWakeup(bool physical, bool logical);
+extern void WalSndTerminate(void);
 extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index fef9f555310..686f8a45494 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -84,3 +84,4 @@ PG_LWLOCK(50, DSMRegistry)
 PG_LWLOCK(51, InjectionPoint)
 PG_LWLOCK(52, SerialControl)
 PG_LWLOCK(53, BgTaskControl)
+PG_LWLOCK(54, WalLevelControl)
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 022fd8ed933..fc5b238ec20 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -54,6 +54,8 @@ typedef enum
 typedef enum
 {
 	PROCSIGNAL_BARRIER_SMGRRELEASE, /* ask smgr to close files */
+	PROCSIGNAL_BARRIER_UPDATE_WAL_LOGGING_STATE,	/* ask to update xlog info
+													 * state */
 } ProcSignalBarrierType;
 
 /*
diff --git a/src/include/utils/bgtask.h b/src/include/utils/bgtask.h
index d9d89811c98..860da74f424 100644
--- a/src/include/utils/bgtask.h
+++ b/src/include/utils/bgtask.h
@@ -22,11 +22,11 @@
  */
 typedef enum
 {
-	BGTASK_TYPE_DUMMY = 0,
+	BGTASK_TYPE_WAL_LEVEL_CHANGE = 0,
 }			BgTaskType;
 
 /* The number of internal background tasks */
-#define NUM_INTERNAL_BGTASKS	BGTASK_TYPE_DUMMY + 1
+#define NUM_INTERNAL_BGTASKS 	BGTASK_TYPE_WAL_LEVEL_CHANGE + 1
 
 typedef void (*bgtask_fn_type) (void);
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 87999218d68..b8d36e0a9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -169,6 +169,8 @@ extern bool check_wal_buffers(int *newval, void **extra, GucSource source);
 extern bool check_wal_consistency_checking(char **newval, void **extra,
 										   GucSource source);
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
+extern const char *show_wal_level(void);
+extern bool check_wal_level(int *newval, void **extra, GucSource source);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
-- 
2.43.5