diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b8f9bf573ea..16310cf474f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -8088,8 +8088,8 @@ SCRAM-SHA-256$<iteration count>:&l
If true, the information (e.g., dead tuples, commit timestamps, and
- origins) on the subscriber that is still useful for conflict detection
- is retained.
+ origins) on the subscriber that is useful for conflict detection is\
+ retained.
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index be90088bcd0..0e49bf09eca 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -443,18 +443,18 @@ CREATE SUBSCRIPTION subscription_name
Specifies whether the information (e.g., dead tuples, commit
- timestamps, and origins) on the subscriber that is still useful for
- conflict detection is retained. The default is
- false. If set to true, an additional replication
- slot named pg_conflict_detection
+ timestamps, and origins) required for conflict detection on the
+ subscriber is retained. The default is false.
+ If set to true, a replication slot named
+ pg_conflict_detection
will be created on the subscriber to prevent the conflict information
from being removed.
Note that the information useful for conflict detection is retained
- only after the creation of the additional slot. You can verify the
- existence of this slot by querying pg_replication_slots.
+ only after the creation of the slot. You can verify the existence of
+ this slot by querying pg_replication_slots.
And even if multiple subscriptions on one node enable this option,
only one replication slot will be created.
@@ -468,7 +468,7 @@ CREATE SUBSCRIPTION subscription_name
- This option cannot be enabled if the publisher is also a physical standby.
+ This option cannot be enabled if the publisher is a physical standby.
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 9938fbe3e57..396cfcce1f6 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -72,7 +72,7 @@
#define SUBOPT_PASSWORD_REQUIRED 0x00000800
#define SUBOPT_RUN_AS_OWNER 0x00001000
#define SUBOPT_FAILOVER 0x00002000
-#define SUBOPT_RETAIN_CONFLICT_INFO 0x00004000
+#define SUBOPT_RETAIN_CONFLICT_INFO 0x00004000
#define SUBOPT_LSN 0x00008000
#define SUBOPT_ORIGIN 0x00010000
@@ -625,6 +625,7 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
errmsg("password_required=false is superuser-only"),
errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
+ /* Ensure that we can enable retainconflictinfo. */
CheckSubConflictInfoRetention(opts.retainconflictinfo, true,
!opts.enabled);
@@ -1084,10 +1085,15 @@ CheckAlterSubOption(Subscription *sub, const char *option,
* publisher cannot be modified if the slot is currently acquired by the
* existing walsender.
*
- * Do not allow changing the retain_conflict_info option when the
- * subscription is enabled or the apply worker is active, to prevent race
- * conditions arising from the new option value being acknowledged
- * asynchronously by the launcher and apply workers.
+ * Note that two_phase is enabled (aka changed from 'false' to 'true') on
+ * the publisher by the existing walsender, so we could have allowed that
+ * even when the subscription is enabled. But we kept this restriction for
+ * the sake of consistency and simplicity.
+ *
+ * Additionally, do not allow changing the retain_conflict_info option when
+ * the subscription is enabled to prevent race conditions arising from the
+ * new option value being acknowledged asynchronously by the launcher and
+ * apply workers.
*
* Without the restriction, a race condition may arise when a user
* disables and immediately re-enables the retain_conflict_info option. In
@@ -1118,11 +1124,6 @@ CheckAlterSubOption(Subscription *sub, const char *option,
* to avoid the race conditions described above, but we maintain the
* restriction for both enable and disable operations for the sake of
* consistency.
- *
- * Note that two_phase is enabled (aka changed from 'false' to 'true') on
- * the publisher by the existing walsender, so we could have allowed that
- * even when the subscription is enabled. But we kept this restriction for
- * the sake of consistency and simplicity.
*/
if (sub->enabled)
ereport(ERROR,
@@ -1399,7 +1400,7 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
* subscription has been disabled.
*
* Ensure workers have already been exited to avoid the
- * race conditions as described in CheckAlterSubOption().
+ * race conditions described in CheckAlterSubOption().
*/
if (logicalrep_workers_find(subid, true, true))
ereport(ERROR,
@@ -1417,12 +1418,8 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
Assert(!sub->enabled);
/*
- * Provide a notice if retain_conflict_info is enabled for
- * a disabled subscription, reminding the user to enable
- * the subscription to prevent the accumulation of dead
- * tuples. A warning is not issued since
- * retain_conflict_info can be altered only for disabled
- * subscriptions.
+ * Remind the user that enabling subscription will prevent
+ * the accumulation of dead tuples.
*/
if (opts.retainconflictinfo)
ereport(NOTICE,
@@ -1461,10 +1458,7 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot enable subscription that does not have a slot name")));
- /*
- * Perform the track_commit_timestamp check only when enabling
- * the subscription.
- */
+ /* Check track_commit_timestamp only when enabling the subscription. */
CheckSubConflictInfoRetention(sub->retainconflictinfo,
opts.enabled, !opts.enabled);
@@ -2341,14 +2335,15 @@ check_publications_origin(WalReceiverConn *wrconn, List *publications,
}
/*
- * Check if the publisher's status permits enabling retain_conflict_info.
+ * Determine whether the retain_conflict_info can be enable based on the
+ * publisher's status.
*
- * Enabling retain_conflict_info is not allowed if the publisher's version is
- * prior to PG18 or if the publisher is in recovery (operating as a standby
+ * This option is disallowed if the publisher is running a version earlier
+ * than the PG19, or if the publisher is in recovery (i.e., it is a standby
* server).
*
- * Refer to the comments atop maybe_advance_nonremovable_xid() for detailed
- * reasons.
+ * See comments atop maybe_advance_nonremovable_xid() for a detailed
+ * explanation.
*/
static void
check_pub_conflict_info_retention(WalReceiverConn *wrconn, bool retain_conflict_info)
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 799daa247e2..aa763fc5ef2 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1339,7 +1339,7 @@ compute_min_nonremovable_xid(LogicalRepWorker *worker,
/*
* Create a replication slot to retain information necessary for
* conflict detection such as dead tuples, commit timestamps, and
- * origins if requested by any subscription.
+ * origins.
*
* The slot is created before starting the apply worker to prevent it
* from unnecessarily maintaining its oldest_nonremovable_xid.
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index f7434b0f6cd..10e1cb7ea45 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -5257,18 +5257,20 @@ InitializeLogRepWorker(void)
}
/*
- * Restart the worker if retain_conflict_info was enabled at startup. The
- * replication slot for conflict detection may not be created yet, or
- * might soon be dropped as the launcher sees retain_conflict_info as
- * disabled. To prevent unnecessary maintenance of oldest_nonremovable_xid
- * when the slot is absent or at risk of being dropped, a restart is
- * initiated.
+ * Restart the worker if retain_conflict_info was enabled during startup.
+ *
+ * At this point, the replication slot used for conflict detection might
+ * not exist yet, or could be dropped soon if the launcher perceives
+ * retain_conflict_info as disabled. To avoid unnecessary tracking of
+ * oldest_nonremovable_xid when the slot is absent or at risk of being
+ * dropped, a restart is initiated.
*
* The oldest_nonremovable_xid should be initialized only when the
* retain_conflict_info is enabled before launching the worker. See
* logicalrep_worker_launch.
*/
- if (am_leader_apply_worker() && MySubscription->retainconflictinfo &&
+ if (am_leader_apply_worker() &&
+ MySubscription->retainconflictinfo &&
!TransactionIdIsValid(MyLogicalRepWorker->oldest_nonremovable_xid))
{
ereport(LOG,