v12-0002-Maintain-the-replication-slot-in-logical-launche.patch
application/octet-stream
Filename: v12-0002-Maintain-the-replication-slot-in-logical-launche.patch
Type: application/octet-stream
Part: 2
Patch
Format: format-patch
Series: patch v12-0002
Subject: Maintain the replication slot in logical launcher to retain dead tuples
| File | + | − |
|---|---|---|
| src/backend/replication/logical/launcher.c | 211 | 0 |
| src/backend/replication/slot.c | 13 | 0 |
| src/backend/replication/slotfuncs.c | 4 | 0 |
| src/backend/replication/walsender.c | 2 | 0 |
| src/include/replication/slot.h | 8 | 0 |
From b8a3ea39d3c7e2c6f1c3c98ba16fd980e87aed05 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <houzj.fnst@cn.fujitsu.com>
Date: Thu, 26 Sep 2024 12:11:34 +0800
Subject: [PATCH v12 2/3] Maintain the replication slot in logical launcher to
retain dead tuples
This patch enables the logical replication launcher to create and maintain a
replication slot named pg_conflict_detection.
The launcher periodically collects the oldest_nonremovable_xid from all apply
workers. It then computes the minimum transaction ID and advances the xmin
value of the replication slot if it precedes the computed value.
The interval for updating the slot (nap time) is dynamically adjusted based on
the activity of the apply workers. The launcher waits for a certain period
before performing the next update, with the duration varying depending on
whether the xmin value of the replication slot was updated during the last
cycle.
---
src/backend/replication/logical/launcher.c | 211 +++++++++++++++++++++
src/backend/replication/slot.c | 13 ++
src/backend/replication/slotfuncs.c | 4 +
src/backend/replication/walsender.c | 2 +
src/include/replication/slot.h | 8 +
5 files changed, 238 insertions(+)
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index e5fdca8bbf..aeb51dacd7 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -46,6 +46,12 @@
/* max sleep time between cycles (3min) */
#define DEFAULT_NAPTIME_PER_CYCLE 180000L
+/*
+ * Min sleep time (200ms) between cycles to update the xmin value of the
+ * replication slot.
+ */
+#define MIN_NAPTIME_PER_SLOT_UPDATE 200
+
/* GUC variables */
int max_logical_replication_workers = 4;
int max_sync_workers_per_subscription = 2;
@@ -100,6 +106,9 @@ static int logicalrep_pa_worker_count(Oid subid);
static void logicalrep_launcher_attach_dshmem(void);
static void ApplyLauncherSetWorkerStartTime(Oid subid, TimestampTz start_time);
static TimestampTz ApplyLauncherGetWorkerStartTime(Oid subid);
+static bool advance_conflict_slot_xmin(FullTransactionId new_xmin);
+static void compute_slot_update_naptime(bool slot_updated, long *sleep_time);
+static void drop_conflict_slot_if_exists(void);
/*
@@ -1127,6 +1136,9 @@ ApplyLauncherWakeup(void)
void
ApplyLauncherMain(Datum main_arg)
{
+ bool slot_maybe_exist = true;
+ long slot_update_wait_time = MIN_NAPTIME_PER_SLOT_UPDATE;
+
ereport(DEBUG1,
(errmsg_internal("logical replication launcher started")));
@@ -1155,6 +1167,8 @@ ApplyLauncherMain(Datum main_arg)
MemoryContext subctx;
MemoryContext oldctx;
long wait_time = DEFAULT_NAPTIME_PER_CYCLE;
+ bool can_advance_xmin = true;
+ FullTransactionId xmin = InvalidFullTransactionId;
CHECK_FOR_INTERRUPTS();
@@ -1175,14 +1189,43 @@ ApplyLauncherMain(Datum main_arg)
long elapsed;
if (!sub->enabled)
+ {
+ can_advance_xmin = false;
continue;
+ }
LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
w = logicalrep_worker_find(sub->oid, InvalidOid, false);
LWLockRelease(LogicalRepWorkerLock);
if (w != NULL)
+ {
+ /*
+ * Collect non-removable transaction IDs from all apply
+ * workers to determine the xmin for advancing the replication
+ * slot used in conflict detection.
+ */
+ if (can_advance_xmin)
+ {
+ FullTransactionId nonremovable_xid;
+
+ SpinLockAcquire(&w->relmutex);
+ nonremovable_xid = w->oldest_nonremovable_xid;
+ SpinLockRelease(&w->relmutex);
+
+ /*
+ * Stop advancing xmin if an invalid non-removable
+ * transaction ID is found, otherwise update xmin.
+ */
+ if (!FullTransactionIdIsValid(nonremovable_xid))
+ can_advance_xmin = false;
+ else if (!FullTransactionIdIsValid(xmin) ||
+ FullTransactionIdPrecedes(nonremovable_xid, xmin))
+ xmin = nonremovable_xid;
+ }
+
continue; /* worker is running already */
+ }
/*
* If the worker is eligible to start now, launch it. Otherwise,
@@ -1215,6 +1258,35 @@ ApplyLauncherMain(Datum main_arg)
}
}
+ /*
+ * Maintain the xmin value of the replication slot for conflict
+ * detection if needed, and update the sleep time before the next
+ * attempt.
+ */
+ if (sublist)
+ {
+ bool updated;
+
+ if (!can_advance_xmin)
+ xmin = InvalidFullTransactionId;
+
+ updated = advance_conflict_slot_xmin(xmin);
+
+ compute_slot_update_naptime(updated, &slot_update_wait_time);
+ wait_time = Min(wait_time, slot_update_wait_time);
+
+ slot_maybe_exist = true;
+ }
+
+ /*
+ * Drop the slot if we're no longer retaining dead tuples.
+ */
+ else if (slot_maybe_exist)
+ {
+ drop_conflict_slot_if_exists();
+ slot_maybe_exist = false;
+ }
+
/* Switch back to original memory context. */
MemoryContextSwitchTo(oldctx);
/* Clean the temporary memory. */
@@ -1242,6 +1314,145 @@ ApplyLauncherMain(Datum main_arg)
/* Not reachable */
}
+/*
+ * Attempt to advance the xmin value of the replication slot used to retain
+ * dead tuples for conflict detection.
+ */
+static bool
+advance_conflict_slot_xmin(FullTransactionId new_xmin)
+{
+ TransactionId next_xid;
+ FullTransactionId next_full_xid;
+ FullTransactionId full_xmin;
+ uint32 xmin_epoch;
+
+ /*
+ * Acquire the slot if it hasn't been acquired yet. If the slot is not yet
+ * created, create it.
+ */
+ if (!MyReplicationSlot)
+ {
+ if (SearchNamedReplicationSlot(CONFLICT_DETECTION_SLOT, true))
+ {
+ ReplicationSlotAcquire(CONFLICT_DETECTION_SLOT, true);
+ }
+ else
+ {
+ TransactionId xmin_horizon;
+
+ ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false,
+ RS_PERSISTENT, false, false, false);
+
+ LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+
+ xmin_horizon = GetOldestSafeDecodingTransactionId(false);
+
+ SpinLockAcquire(&MyReplicationSlot->mutex);
+ MyReplicationSlot->effective_xmin = xmin_horizon;
+ MyReplicationSlot->data.xmin = xmin_horizon;
+ SpinLockRelease(&MyReplicationSlot->mutex);
+
+ ReplicationSlotsComputeRequiredXmin(true);
+
+ LWLockRelease(ProcArrayLock);
+
+ ReplicationSlotMarkDirty();
+ ReplicationSlotSave();
+ }
+ }
+
+ if (!FullTransactionIdIsValid(new_xmin))
+ return false;
+
+ Assert(TransactionIdIsValid(MyReplicationSlot->data.xmin));
+
+ /*
+ * Compute the epoch of the xmin value for the replication slot based on
+ * the next full transaction ID and its epoch.
+ */
+ next_full_xid = ReadNextFullTransactionId();
+ next_xid = XidFromFullTransactionId(next_full_xid);
+ xmin_epoch = EpochFromFullTransactionId(next_full_xid);
+
+ /*
+ * Adjust the epoch if the next transaction ID is less than the current
+ * xmin of the replication slot. This handles the case where transaction
+ * ID wraparound has occurred.
+ */
+ if (next_xid < MyReplicationSlot->data.xmin)
+ xmin_epoch--;
+
+ full_xmin = FullTransactionIdFromEpochAndXid(xmin_epoch,
+ MyReplicationSlot->data.xmin);
+
+ if (FullTransactionIdPrecedesOrEquals(new_xmin, full_xmin))
+ return false;
+
+ SpinLockAcquire(&MyReplicationSlot->mutex);
+ MyReplicationSlot->data.xmin = XidFromFullTransactionId(new_xmin);
+ SpinLockRelease(&MyReplicationSlot->mutex);
+
+ /* first write new xmin to disk, so we know what's up after a crash */
+
+ ReplicationSlotMarkDirty();
+ ReplicationSlotSave();
+ elog(DEBUG1, "updated xmin: %u", MyReplicationSlot->data.xmin);
+
+ /*
+ * Now the new xmin is safely on disk, we can let the global value
+ * advance. We do not take ProcArrayLock or similar since we only advance
+ * xmin here and there's not much harm done by a concurrent computation
+ * missing that.
+ */
+ SpinLockAcquire(&MyReplicationSlot->mutex);
+ MyReplicationSlot->effective_xmin = MyReplicationSlot->data.xmin;
+ SpinLockRelease(&MyReplicationSlot->mutex);
+
+ ReplicationSlotsComputeRequiredXmin(false);
+
+ return true;
+}
+
+/*
+ * Update the sleep time before the next slot update.
+ *
+ * If there is no slot activity, the wait time between sync cycles will double
+ * (up to a maximum of 3 minutes). If there is some slot activity, the wait
+ * time between sync cycles is reset to the minimum (200ms).
+ */
+static void
+compute_slot_update_naptime(bool slot_updated, long *sleep_time)
+{
+ if (!slot_updated)
+ {
+ /*
+ * The slot was not updated, so double the sleep time, but not beyond
+ * the maximum allowable value.
+ */
+ *sleep_time = Min(*sleep_time * 2, DEFAULT_NAPTIME_PER_CYCLE);
+ }
+ else
+ {
+ /*
+ * The slot was updated since the last sleep, so reset the sleep time.
+ */
+ *sleep_time = MIN_NAPTIME_PER_SLOT_UPDATE;
+ }
+}
+
+/*
+ * Drop the replication slot used to retain dead tuples for conflict detection,
+ * if it exists.
+ */
+static void
+drop_conflict_slot_if_exists(void)
+{
+ if (MyReplicationSlot)
+ ReplicationSlotDropAcquired();
+ else if (SearchNamedReplicationSlot(CONFLICT_DETECTION_SLOT, true))
+ ReplicationSlotDrop(CONFLICT_DETECTION_SLOT, true);
+}
+
/*
* Is current process the logical replication launcher?
*/
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 6828100cf1..7d6b74c39f 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -288,6 +288,19 @@ ReplicationSlotValidateName(const char *name, int elevel)
return true;
}
+/*
+ * Report an error if the replication slot name is "pg_conflict_detection".
+ */
+void
+ErrorOnReservedSlotName(const char *name)
+{
+ if (strcmp(name, CONFLICT_DETECTION_SLOT) == 0)
+ ereport(ERROR,
+ errcode(ERRCODE_RESERVED_NAME),
+ errmsg("replication slot name \"%s\" is reserved",
+ name));
+}
+
/*
* Create a new replication slot and mark it as used by this backend.
*
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 488a161b3e..d547d98124 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -38,6 +38,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
{
Assert(!MyReplicationSlot);
+ ErrorOnReservedSlotName(name);
+
/* acquire replication slot, this will check for conflicting names */
ReplicationSlotCreate(name, false,
temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
@@ -124,6 +126,8 @@ create_logical_replication_slot(char *name, char *plugin,
Assert(!MyReplicationSlot);
+ ErrorOnReservedSlotName(name);
+
/*
* Acquire a logical decoding slot, this will check for conflicting names.
* Initially create persistent slot as ephemeral - that allows us to
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 00b6411c7e..8c18264f58 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1190,6 +1190,8 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
&failover);
+ ErrorOnReservedSlotName(cmd->slotname);
+
if (cmd->kind == REPLICATION_KIND_PHYSICAL)
{
ReplicationSlotCreate(cmd->slotname, false,
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 45582cf9d8..0cfcbb38a0 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -20,6 +20,13 @@
/* directory to store replication slot data in */
#define PG_REPLSLOT_DIR "pg_replslot"
+/*
+ * The reserved name for a replication slot used to retain dead tuples for
+ * conflict detection in logical replication. See
+ * maybe_advance_nonremovable_xid() for detail.
+ */
+#define CONFLICT_DETECTION_SLOT "pg_conflict_detection"
+
/*
* Behaviour of replication slots, upon release or crash.
*
@@ -258,6 +265,7 @@ extern void ReplicationSlotMarkDirty(void);
/* misc stuff */
extern void ReplicationSlotInitialize(void);
extern bool ReplicationSlotValidateName(const char *name, int elevel);
+extern void ErrorOnReservedSlotName(const char *name);
extern void ReplicationSlotReserveWal(void);
extern void ReplicationSlotsComputeRequiredXmin(bool already_locked);
extern void ReplicationSlotsComputeRequiredLSN(void);
--
2.31.1