From 3047c7473df8f3be43859c1bec74c99fba80ecf0 Mon Sep 17 00:00:00 2001 From: Zhijie Hou Date: Fri, 3 Apr 2026 13:44:40 +0800 Subject: [PATCH v1] write tablesync changes with the subscription origin ID During initial table synchronization, tablesync workers were writing tuples with the per-table tablesync origin identity. Later, when the leader apply worker processed UPDATE/DELETE for those tuples, conflict checks could see an origin mismatch and report benign update_origin_differs or delete_origin_differs noise for changes from the same subscription. Fix this by keeping the tablesync origin for per-table sync progress tracking and resume but stamp tuple writes with the subscription-level apply origin identity. This ensures conflict detection sees tablesync and apply changes as coming from the same subscription. --- src/backend/replication/logical/tablesync.c | 27 ++++++++++++++++++--- src/test/subscription/t/029_on_error.pl | 9 +++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c index f49a4852ecb..4aa39341e8e 100644 --- a/src/backend/replication/logical/tablesync.c +++ b/src/backend/replication/logical/tablesync.c @@ -1226,6 +1226,7 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos) AclResult aclresult; WalRcvExecResult *res; char originname[NAMEDATALEN]; + char applyoriginname[NAMEDATALEN]; ReplOriginId originid; UserContext ucxt; bool must_use_password; @@ -1285,12 +1286,17 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos) MyLogicalRepWorker->relstate == SUBREL_STATE_DATASYNC || MyLogicalRepWorker->relstate == SUBREL_STATE_FINISHEDCOPY); - /* Assign the origin tracking record name. */ + /* Assign the origin tracking record names. */ ReplicationOriginNameForLogicalRep(MySubscription->oid, MyLogicalRepWorker->relid, originname, sizeof(originname)); + ReplicationOriginNameForLogicalRep(MySubscription->oid, + InvalidOid, + applyoriginname, + sizeof(applyoriginname)); + if (MyLogicalRepWorker->relstate == SUBREL_STATE_DATASYNC) { /* @@ -1320,7 +1326,15 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos) */ originid = replorigin_by_name(originname, false); replorigin_session_setup(originid, 0); - replorigin_xact_state.origin = originid; + + /* + * Tablesync keeps its own origin for replication progress, but writes + * must be tagged with the subscription-level apply origin so conflict + * detection sees tablesync and apply changes as coming from the same + * subscription. + */ + replorigin_xact_state.origin = replorigin_by_name(applyoriginname, false); + *origin_startpos = replorigin_session_get_progress(false); CommitTransactionCommand(); @@ -1407,7 +1421,14 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos) UnlockRelationOid(ReplicationOriginRelationId, RowExclusiveLock); replorigin_session_setup(originid, 0); - replorigin_xact_state.origin = originid; + + /* + * Tablesync keeps its own origin for replication progress, but writes + * must be tagged with the subscription-level apply origin so conflict + * detection sees tablesync and apply changes as coming from the same + * subscription. + */ + replorigin_xact_state.origin = replorigin_by_name(applyoriginname, false); /* * If the user did not opt to run as the owner of the subscription diff --git a/src/test/subscription/t/029_on_error.pl b/src/test/subscription/t/029_on_error.pl index 7d68759b6cd..85d3478f44f 100644 --- a/src/test/subscription/t/029_on_error.pl +++ b/src/test/subscription/t/029_on_error.pl @@ -146,6 +146,8 @@ COMMIT; test_skip_lsn($node_publisher, $node_subscriber, "(2, NULL)", "2", "test skipping transaction"); +my $log_location = -s $node_subscriber->logfile; + # Test for PREPARE and COMMIT PREPARED. Update the data and PREPARE the # transaction, raising an error on the subscriber due to violation of the # unique constraint on tbl. Then skip the transaction. @@ -160,6 +162,13 @@ COMMIT PREPARED 'gtx'; test_skip_lsn($node_publisher, $node_subscriber, "(3, NULL)", "3", "test skipping prepare and commit prepared "); +# Check that no update_origin_differs conflicts are raised +my $logfile = slurp_file($node_subscriber->logfile(), $log_location); +unlike( + $logfile, + qr/conflict detected on relation "public.tbl": conflict=update_origin_differs.*/, + 'modifying the row copied by tablesync should not cause update_origin_differs conflict'); + # Test for STREAM COMMIT. Insert enough rows to tbl to exceed the 64kB # limit, also raising an error on the subscriber during applying spooled # changes for the same reason. Then skip the transaction. -- 2.53.0.windows.2