v2-0002-Maintain-the-oldest-non-removeable-tranasction-id.patch
application/octet-stream
Filename: v2-0002-Maintain-the-oldest-non-removeable-tranasction-id.patch
Type: application/octet-stream
Part: 1
Patch
Format: format-patch
Series: patch v2-0002
Subject: Maintain the oldest non removeable tranasction id by apply worker
| File | + | − |
|---|---|---|
| doc/src/sgml/protocol.sgml | 52 | 0 |
| src/backend/replication/logical/worker.c | 181 | 1 |
| src/backend/replication/walsender.c | 23 | 0 |
| src/include/replication/worker_internal.h | 18 | 0 |
| src/tools/pgindent/typedefs.list | 1 | 0 |
From cc780560d69268cad6ec465cb12eb2f709938ff2 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <houzj.fnst@cn.fujitsu.com>
Date: Thu, 26 Sep 2024 14:11:40 +0800
Subject: [PATCH v2 2/5] Maintain the oldest non removeable tranasction id by
apply worker
This patch allows each apply worker to maintain the oldest_nonremovable_xid in
the shared memory. The xid is advanced based on the following process:
1) Calling GetRunningTransactionData() to take oldestRunningXid as the
candidate xid and send a new message to request the remote WAL position from
the walsender.
2) It then waits (non-blocking) to receive the WAL position from the walsender.
3) After receiving the WAL position, the non-removable transaction ID is
advanced if the current flush location has reached or surpassed the received
WAL position.
These steps are repeated at intervals defined by wal_receiver_status_interval
to minimize performance impact.
---
doc/src/sgml/protocol.sgml | 52 +++++++
src/backend/replication/logical/worker.c | 182 +++++++++++++++++++++-
src/backend/replication/walsender.c | 23 +++
src/include/replication/worker_internal.h | 18 +++
src/tools/pgindent/typedefs.list | 1 +
5 files changed, 275 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 11b6456779..4896086220 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2440,6 +2440,41 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
</variablelist>
</listitem>
</varlistentry>
+
+ <varlistentry id="protocol-replication-primary-wal-status-update">
+ <term>Primary WAL status update (B)</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Byte1('s')</term>
+ <listitem>
+ <para>
+ Identifies the message as a primary WAL status update.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Int64</term>
+ <listitem>
+ <para>
+ The latest WAL write position on the server.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Int64</term>
+ <listitem>
+ <para>
+ The server's system clock at the time of transmission, as
+ microseconds since midnight on 2000-01-01.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
</variablelist>
<para>
@@ -2584,6 +2619,23 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
</variablelist>
</listitem>
</varlistentry>
+
+ <varlistentry id="protocol-replication-standby-wal-status-request">
+ <term>Standby WAL status request (F)</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Byte1('x')</term>
+ <listitem>
+ <para>
+ Identifies the message as a request for the WAL status on the primary.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</listitem>
</varlistentry>
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 925dff9cc4..d74d8fe2de 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -173,12 +173,14 @@
#include "replication/logicalrelation.h"
#include "replication/logicalworker.h"
#include "replication/origin.h"
+#include "replication/slot.h"
#include "replication/walreceiver.h"
#include "replication/worker_internal.h"
#include "rewrite/rewriteHandler.h"
#include "storage/buffile.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
+#include "storage/procarray.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/dynahash.h"
@@ -275,6 +277,19 @@ typedef enum
TRANS_PARALLEL_APPLY,
} TransApplyAction;
+/*
+ * The phases involved in advancing the non-removable transaction ID.
+ *
+ * Refer to maybe_advance_nonremovable_xid() for details on how the function
+ * transitions between these phases.
+ */
+typedef enum
+{
+ DTR_REQUEST_WALSENDER_WAL_POS,
+ DTR_WAIT_FOR_WALSENDER_WAL_POS,
+ DTR_WAIT_FOR_LOCAL_FLUSH
+} DeadTupleRetainPhase;
+
/* errcontext tracker */
static ApplyErrorCallbackArg apply_error_callback_arg =
{
@@ -339,6 +354,8 @@ static XLogRecPtr skip_xact_finish_lsn = InvalidXLogRecPtr;
/* BufFile handle of the current streaming file */
static BufFile *stream_fd = NULL;
+static XLogRecPtr last_flushpos = InvalidXLogRecPtr;
+
typedef struct SubXactInfo
{
TransactionId xid; /* XID of the subxact */
@@ -378,6 +395,8 @@ static void stream_open_and_write_change(TransactionId xid, char action, StringI
static void stream_close_file(void);
static void send_feedback(XLogRecPtr recvpos, bool force, bool requestReply);
+static void maybe_advance_nonremovable_xid(XLogRecPtr *remote_wal_pos,
+ DeadTupleRetainPhase *phase);
static void apply_handle_commit_internal(LogicalRepCommitData *commit_data);
static void apply_handle_insert_internal(ApplyExecutionData *edata,
@@ -3573,6 +3592,8 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
bool ping_sent = false;
TimeLineID tli;
ErrorContextCallback errcallback;
+ XLogRecPtr remote_lsn = InvalidXLogRecPtr;
+ DeadTupleRetainPhase phase = DTR_REQUEST_WALSENDER_WAL_POS;
/*
* Init the ApplyMessageContext which we clean up after each replication
@@ -3694,6 +3715,16 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
send_feedback(last_received, reply_requested, false);
UpdateWorkerStats(last_received, timestamp, true);
}
+ else if (c == 's')
+ {
+ TimestampTz timestamp;
+
+ remote_lsn = pq_getmsgint64(&s);
+ timestamp = pq_getmsgint64(&s);
+
+ maybe_advance_nonremovable_xid(&remote_lsn, &phase);
+ UpdateWorkerStats(last_received, timestamp, false);
+ }
/* other message types are purposefully ignored */
MemoryContextReset(ApplyMessageContext);
@@ -3706,6 +3737,8 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
/* confirm all writes so far */
send_feedback(last_received, false, false);
+ maybe_advance_nonremovable_xid(&remote_lsn, &phase);
+
if (!in_remote_transaction && !in_streamed_transaction)
{
/*
@@ -3838,7 +3871,6 @@ send_feedback(XLogRecPtr recvpos, bool force, bool requestReply)
static XLogRecPtr last_recvpos = InvalidXLogRecPtr;
static XLogRecPtr last_writepos = InvalidXLogRecPtr;
- static XLogRecPtr last_flushpos = InvalidXLogRecPtr;
XLogRecPtr writepos;
XLogRecPtr flushpos;
@@ -3916,6 +3948,154 @@ send_feedback(XLogRecPtr recvpos, bool force, bool requestReply)
last_flushpos = flushpos;
}
+/*
+ * Attempt to advance the non-removable transaction id.
+ *
+ * The oldest_nonremovable_xid is maintained in shared memory to prevent dead
+ * rows from being removed prematurely when the apply worker still needs them
+ * to detect update-delete conflicts.
+ *
+ * The non-removable transaction id is advanced to the oldest running
+ * transaction id once all concurrent transactions on the publisher have been
+ * applied and flushed locally. The process involves:
+ *
+ * DTR_REQUEST_WALSENDER_WAL_POS - Call GetRunningTransactionData() to get the
+ * candidate xmin and send a message to request the remote WAL position from
+ * the walsender.
+ *
+ * DTR_WAIT_FOR_WALSENDER_WAL_POS - Wait for receiving the WAL position from
+ * the walsender.
+ *
+ * DTR_WAIT_FOR_LOCAL_FLUSH - Advance the non-removable transaction ID if the
+ * current flush location has reached or surpassed the received WAL position.
+ *
+ * Retaining the dead tuples for this period is sufficient because any
+ * subsequent transaction from the publisher will have a later timestamp.
+ * Therefore, it is acceptable if dead tuples are removed by vacuum and an
+ * update_missing conflict is detected, as the correct resolution for the
+ * last-update-wins strategy in this case is to convert the UPDATE to an INSERT
+ * and apply it anyway.
+ *
+ * The 'remote_wal_pos' will be reset after sending a new request to walsender.
+ */
+static void
+maybe_advance_nonremovable_xid(XLogRecPtr *remote_wal_pos,
+ DeadTupleRetainPhase *phase)
+{
+ static TimestampTz xid_advance_attemp_time = 0;
+ static FullTransactionId candidate_xid;
+
+ Assert(remote_wal_pos);
+
+ /* Exit early if retaining dead tuples is not required */
+ if (!MySubscription->retaindeadtuples)
+ return;
+
+ if (*phase == DTR_WAIT_FOR_WALSENDER_WAL_POS)
+ {
+ Assert(xid_advance_attemp_time);
+
+ /*
+ * Return if we have requested but not yet received the remote wal
+ * position.
+ */
+ if (XLogRecPtrIsInvalid(*remote_wal_pos))
+ return;
+
+ *phase = DTR_WAIT_FOR_LOCAL_FLUSH;
+ }
+
+ if (*phase == DTR_WAIT_FOR_LOCAL_FLUSH)
+ {
+ Assert(!XLogRecPtrIsInvalid(*remote_wal_pos) &&
+ FullTransactionIdIsValid(candidate_xid));
+
+ /*
+ * Do not attempt to advance the non-removable transaction id when
+ * table sync is in progress. During this time, changes from a single
+ * transaction may be applied by multiple table sync workers
+ * corresponding to the target tables. In this case, confirming the
+ * apply and flush progress across all table sync workers is complex
+ * and not worth the effort.
+ */
+ if (!AllTablesyncsReady())
+ return;
+
+ /* Return to wait for the changes to be applied */
+ if (last_flushpos < *remote_wal_pos)
+ return;
+
+ /*
+ * Advance the non-removable transaction id if the remote wal position
+ * has been received, and all transactions up to that position on the
+ * publisher have been applied and flushed locally.
+ */
+ SpinLockAcquire(&MyLogicalRepWorker->relmutex);
+ MyLogicalRepWorker->oldest_nonremovable_xid = candidate_xid;
+ SpinLockRelease(&MyLogicalRepWorker->relmutex);
+
+ *phase = DTR_REQUEST_WALSENDER_WAL_POS;
+ }
+
+ if (*phase == DTR_REQUEST_WALSENDER_WAL_POS)
+ {
+ TimestampTz now;
+ RunningTransactions running_transactions;
+ TransactionId oldest_running_xid;
+ FullTransactionId full_xid;
+ uint32 epoch;
+
+ /*
+ * Exit early if the user has disabled sending messages to the
+ * publisher.
+ */
+ if (wal_receiver_status_interval <= 0)
+ return;
+
+ now = GetCurrentTimestamp();
+
+ /*
+ * Compute the candidate_xid and send a message at most once per
+ * wal_receiver_status_interval.
+ */
+ if (!TimestampDifferenceExceeds(xid_advance_attemp_time, now,
+ wal_receiver_status_interval * 1000))
+ return;
+
+ xid_advance_attemp_time = now;
+
+ running_transactions = GetRunningTransactionData();
+ oldest_running_xid = running_transactions->oldestRunningXid;
+ epoch = EpochFromFullTransactionId(TransamVariables->nextXid);
+
+ /* Release the locks acquired in GetRunningTransactionData() */
+ LWLockRelease(ProcArrayLock);
+ LWLockRelease(XidGenLock);
+
+ /* Compute the epoch of the oldestRunningXid */
+ if (oldest_running_xid > running_transactions->nextXid)
+ epoch--;
+
+ full_xid = FullTransactionIdFromEpochAndXid(epoch,
+ oldest_running_xid);
+
+ /* Return if the new transaction ID is unchanged */
+ if (FullTransactionIdFollowsOrEquals(MyLogicalRepWorker->oldest_nonremovable_xid,
+ full_xid))
+ return;
+
+ candidate_xid = full_xid;
+
+ elog(DEBUG2, "sending wal position request message");
+
+ /* Send a wal position request message to the server */
+ walrcv_send(LogRepWorkerWalRcvConn, "x", sizeof(uint8));
+
+ *remote_wal_pos = InvalidXLogRecPtr;
+ *phase = DTR_WAIT_FOR_WALSENDER_WAL_POS;
+ }
+}
+
/*
* Exit routine for apply workers due to subscription parameter changes.
*/
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index c5f1009f37..5371711488 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -253,6 +253,7 @@ static void StartLogicalReplication(StartReplicationCmd *cmd);
static void ProcessStandbyMessage(void);
static void ProcessStandbyReplyMessage(void);
static void ProcessStandbyHSFeedbackMessage(void);
+static void ProcessStandbyWalPosRequestMessage(void);
static void ProcessRepliesIfAny(void);
static void ProcessPendingWrites(void);
static void WalSndKeepalive(bool requestReply, XLogRecPtr writePtr);
@@ -2342,6 +2343,10 @@ ProcessStandbyMessage(void)
ProcessStandbyHSFeedbackMessage();
break;
+ case 'x':
+ ProcessStandbyWalPosRequestMessage();
+ break;
+
default:
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
@@ -2688,6 +2693,24 @@ ProcessStandbyHSFeedbackMessage(void)
}
}
+/*
+ * Process the standby message requesting the latest WAL write position.
+ */
+static void
+ProcessStandbyWalPosRequestMessage(void)
+{
+ elog(DEBUG2, "sending wal write position");
+
+ /* construct the message... */
+ resetStringInfo(&output_message);
+ pq_sendbyte(&output_message, 's');
+ pq_sendint64(&output_message, GetXLogWriteRecPtr());
+ pq_sendint64(&output_message, GetCurrentTimestamp());
+
+ /* ... and send it wrapped in CopyData */
+ pq_putmessage_noblock('d', output_message.data, output_message.len);
+}
+
/*
* Compute how long send/receive loops should sleep.
*
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 9646261d7e..370c71c93e 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -86,6 +86,24 @@ typedef struct LogicalRepWorker
/* Indicates whether apply can be performed in parallel. */
bool parallel_apply;
+ /*
+ * The changes made by this and later transactions are still non-removable
+ * to allow for the detection of update_delete conflicts when applying
+ * changes in this logical replication worker.
+ *
+ * Note that this info cannot directly protect dead tuples from being
+ * prematurely frozen or removed. The logical replication launcher
+ * asynchronously collects this info to determine whether to advance the
+ * xmin value of the replication slot.
+ *
+ * Therefore, FullTransactionId that includes both the transaction ID and
+ * its epoch is used here instead of a single Transaction ID. This is
+ * critical because without considering the epoch, the transaction ID
+ * alone may appear as if it is in the future due to transaction ID
+ * wraparound.
+ */
+ FullTransactionId oldest_nonremovable_xid;
+
/* Stats. */
XLogRecPtr last_lsn;
TimestampTz last_send_time;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5fabb127d7..66d7f16a73 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -591,6 +591,7 @@ DbInfoArr
DbLocaleInfo
DeClonePtrType
DeadLockState
+DeadTupleRetainPhase
DeallocateStmt
DeclareCursorStmt
DecodedBkpBlock
--
2.30.0.windows.2