RE: Conflict detection for update_deleted in logical replication

From: "Zhijie Hou (Fujitsu)" <houzj(dot)fnst(at)fujitsu(dot)com>
To: Amit Kapila <amit(dot)kapila16(at)gmail(dot)com>
Cc: shveta malik <shveta(dot)malik(at)gmail(dot)com>, vignesh C <vignesh21(at)gmail(dot)com>, Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)com>, Nisha Moond <nisha(dot)moond412(at)gmail(dot)com>, "Hayato Kuroda (Fujitsu)" <kuroda(dot)hayato(at)fujitsu(dot)com>, pgsql-hackers <pgsql-hackers(at)postgresql(dot)org>, Dilip Kumar <dilipbalaut(at)gmail(dot)com>
Subject: RE: Conflict detection for update_deleted in logical replication
Date: 2025-07-04 11:18:38
Message-ID: OS0PR01MB5716FE6FE8FFE8A621F0943B9442A@OS0PR01MB5716.jpnprd01.prod.outlook.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Wed, Jul 2, 2025 at 3:28 PM Hou, Zhijie wrote:
> Kindly use the latest patch set for performance testing.

During testing, we observed a limitation in cascading logical replication
setups, such as (A -> B -> C). When retain_conflict_info is enabled on Node C,
it may not retain information necessary for conflict detection when applying
changes originally replicated from Node A. This happens because Node C only
waits for locally originated changes on Node B to be applied before advancing
the non-removable transaction ID.

For example, Consider a logical replication setup as mentioned above : A -> B -> C.
- All three nodes have a table t1 with two tuples (1,1) (2,2).
- Node B subscribed to all changes of t1 from Node A
- Node-C subscribed to all changes from Node B.
- Subscriptions use the default origin=ANY, as this is not a bidirectional
setup.

Now, consider two concurrent operations:
- @9:00 Node A - UPDATE (1,1) -> (1,11)

- @9:02 Node C - DELETE (1,1)

Assume a slight delay at Node B before it applies the update from Node A.

@9:03 Node C - advances the non-removable XID because it sees no concurrent
transactions from Node B. It is unaware of Node A’s concurrent update.

@9:04 Node B - receives Node A's UPDATE and applies (1,1) -> (1,11)
t1 has tuples : (1,11), (2,2)

@9:05 Node C - receives the UPDATE (1,1) -> (1,11)
- As conflict slot’s xmin is advanced, the deleted tuple may already have
been removed.
- Conflict resolution fails to detect update_deleted and instead raises
update_missing.

Note that, as per decoding logic Node C sees the commit timestamp of the update
as 9:00 (origin commit_ts from Node A), not 9:04 (commit time on Node B). In
this case, since the UPDATE's timestamp is earlier than the DELETE, Node C
should ideally detect an update_deleted conflict. However, it cannot, because
it no longer retains the deleted tuple.

Even if Node C attempts to retrieve the latest WAL position from Node A, Node C
doesn't maintain any LSN which we could use to compare with it.

This scenario is similar to another restriction in the patch where
retain_conflict_info is not supported if the publisher is also a physical
standby, as the required transaction information from the original primary is
unavailable. Moreover, this limitation is relevant only when the subscription
origin option is set to ANY, as only in that case changes from other origins
can be replicated. Since retain_conflict_info is primarily useful for conflict
detection in bidirectional clusters where the origin option is set to NONE,
this limitation appears acceptable.

Given these findings, to help users avoid unintended configurations, we plan to
issue a warning in scenarios where replicated changes may include origins other
than the direct publisher, similar to the existing checks in the
check_publications_origin() function.

Here is the latest patch that implements the warning and documents
this case. Only 0001 is modified for this.

A big thanks to Nisha for invaluable assistance in identifying this
case and preparing the analysis for it.

Best Regards,
Hou zj

Attachment Content-Type Size
v47-0004-Support-the-conflict-detection-for-update_delete.patch application/octet-stream 30.2 KB
v47-0001-Preserve-conflict-relevant-data-during-logical-r.patch application/octet-stream 184.3 KB
v47-0005-Allow-altering-retain_conflict_info-for-enabled-.patch application/octet-stream 32.9 KB
v47-0002-Introduce-a-new-GUC-max_conflict_retention_durat.patch application/octet-stream 31.0 KB
v47-0003-Re-create-the-replication-slot-if-the-conflict-r.patch application/octet-stream 7.0 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Fujii Masao 2025-07-04 11:32:37 Re: Assertion failure in smgr.c when using pg_prewarm with partitioned tables
Previous Message Jakub Wartak 2025-07-04 11:05:05 Re: Adding basic NUMA awareness