diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 7a40c62c59b..16d9171bf0d 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2672,11 +2672,11 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
Int64
- The oldest transaction ID along that is currently in the commit
- phase on the server, along with its epoch. The most significant 32
- bits are the epoch. The least significant 32 bits are the
- transaction ID. If no transactions are active on the server, this
- number will be the next transaction ID to be assigned.
+ The oldest transaction ID that is currently in the commit phase on
+ the server, along with its epoch. The most significant 32 bits are
+ the epoch. The least significant 32 bits are the transaction ID.
+ If no transactions are active on the server, this number will be
+ the next transaction ID to be assigned.
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e9ca6c88e7e..6b9477bdf6a 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -309,17 +309,16 @@ typedef struct RetainConflictInfoData
* (RCI_WAIT_FOR_LOCAL_FLUSH) */
FullTransactionId candidate_xid; /* candidate for the non-removable
* transaction ID */
+ TimestampTz flushpos_update_time; /* when the remote flush position was
+ * updated in final phase
+ * (RCI_WAIT_FOR_LOCAL_FLUSH) */
/*
* The following fields are used to determine the timing for the next round
- * of transaction ID advancement and the update of the remote flush
- * position.
+ * of transaction ID advancement.
*/
TimestampTz last_recv_time; /* when the last message was received */
TimestampTz candidate_xid_time; /* when the candidate_xid is decided */
- TimestampTz flushpos_update_time; /* when the remote flush position was
- * updated in final phase
- * (RCI_WAIT_FOR_LOCAL_FLUSH) */
int xid_advance_interval; /* how much time (ms) to wait before
* attempting to advance the
* non-removable transaction ID */
@@ -4051,7 +4050,10 @@ send_feedback(XLogRecPtr recvpos, bool force, bool requestReply)
*
* 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_deleted conflicts.
+ * to detect update_deleted conflicts. Additionally, this helps to retain
+ * the required commit_ts module information, which further helps to detect
+ * update_origin_differs and delete_origin_differs conflicts reliably, as
+ * otherwise, vacuum freeze could remove the required information.
*
* The non-removable transaction ID is advanced to the oldest running
* transaction ID once all concurrent transactions on the publisher have been
@@ -4106,12 +4108,6 @@ send_feedback(XLogRecPtr recvpos, bool force, bool requestReply)
* subscriber. However, commit timestamps can regress since a commit with a
* later LSN is not guaranteed to have a later timestamp than those with
* earlier LSNs.
- *
- * XXX In phase RCI_REQUEST_PUBLISHER_STATUS, a potential enhancement could be
- * requesting transaction information specifically for those containing
- * UPDATEs. However, this approach introduces additional complexities in
- * tracking UPDATEs for transactions on the publisher, and it may not
- * effectively address scenarios with frequent UPDATEs.
*/
static void
maybe_advance_nonremovable_xid(RetainConflictInfoData *data,
@@ -4201,8 +4197,7 @@ get_candidate_xid(RetainConflictInfoData *data)
/*
* Immediately update the timer, even if the function returns later without
- * setting candidate_xid due to inactivity on the subscriber. This ensures
- * a certain interval before recalculating candidate_xid, minimizing
+ * setting candidate_xid due to inactivity on the subscriber. This avoids
* frequent calls to GetOldestActiveTransactionId.
*/
data->candidate_xid_time = now;
@@ -4298,8 +4293,8 @@ wait_for_publisher_status(RetainConflictInfoData *data, bool status_received)
* However, it requires maintaining two fields, last_remote_nextxid and
* last_remote_lsn, within the structure for comparison with the current
* cycle's values. Considering the minimal cost of continuing in
- * RCI_WAIT_FOR_LOCAL_FLUSH without awaiting changes, we opt not to
- * advance transaction ID in this case.
+ * RCI_WAIT_FOR_LOCAL_FLUSH without awaiting changes, we opted not to
+ * advance the transaction ID here.
*/
if (FullTransactionIdPrecedesOrEquals(data->last_phase_at,
data->remote_oldestxid))
@@ -4399,7 +4394,9 @@ wait_for_local_flush(RetainConflictInfoData *data)
/*
* Reset all data fields except those used to determine the timing for the
- * next round of transaction ID advancement.
+ * next round of transaction ID advancement. We can even use
+ * flushpos_update_time in the next round to decide whether to get the
+ * latest flush position.
*/
data->phase = RCI_GET_CANDIDATE_XID;
data->remote_lsn = InvalidXLogRecPtr;
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c1d5ddbad4b..ce87f40e986 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -2876,8 +2876,8 @@ GetRunningTransactionData(void)
* simple as possible and leave GetSnapshotData() as the primary code for
* that bookkeeping.
*
- * If inCommitOnly is true, we target transactions currently in the commit
- * phase only.
+ * inCommitOnly indicates getting the oldestActiveXid among the transactions
+ * in the commit critical section.
*/
TransactionId
GetOldestActiveTransactionId(bool inCommitOnly)