Re: Avoid using raw xmin of frozen tuples in logicalrep conflict detection

From: Osama Abdul Qader <osamaabdulqaderpostgres(at)gmail(dot)com>
To: Zhijie Hou <houzhijie22(at)gmail(dot)com>
Cc: pgsql-hackers <pgsql-hackers(at)postgresql(dot)org>, Amit Kapila <amit(dot)kapila16(at)gmail(dot)com>
Subject: Re: Avoid using raw xmin of frozen tuples in logicalrep conflict detection
Date: 2026-11-04 13:16:56
Message-ID: CAAprpiBwp3ajkiGB0udGS2ina6sj5mO_NsHOkYyBKESz-N_XgA@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi everyone,

On Wed, 4 Nov, 2026, 6:35 pm Zhijie Hou, <houzhijie22(at)gmail(dot)com> wrote:

> Hi,
>
> While working on another logical replication feature, I noticed a bug: the
> apply worker uses a raw xmin that may have been frozen when fetching tuple
> info
> for conflict detection and reporting.
>
> The reason is that, GetTupleTransactionInfo() used slot_getsysattr() with
> MinTransactionIdAttributeNumber to fetch the local tuple's xmin.
> However, heap_getsysattr() calls HeapTupleHeaderGetRawXmin(), which
> returns the raw 32-bit t_xmin from the tuple header even when the tuple
> is marked frozen (HEAP_XMIN_FROZEN).
>
> Once a tuple is frozen, its raw t_xmin is retained only for debugging
> purposes; its commit timestamp entry may no longer be valid, or the
> 32-bit XID may have wrapped around and been reassigned in a later epoch.
> Using the raw xmin of a frozen tuple could therefore trigger spurious
> update_origin_differs or delete_origin_differs conflicts, or report
> bogus transaction ID, origin, and commit timestamp details for unique
> constraint conflicts.
>
> Attaching a patch fixing this by fetching the tuple via
> ExecFetchSlotHeapTuple() and calling HeapTupleHeaderGetXmin(), returning
> InvalidTransactionId and skipping the commit timestamp lookup when the
> tuple's
> xmin is not a normal transaction ID. Also update
> errdetail_apply_conflict() to
> omit the transaction ID from unique-conflict detail messages when
> localxmin is
> invalid.
>
> --
> Best Regards,
> Zhijie Hou
>

I am ready to contribute, but I've some doubts running with regards to and
design decision.

1. Why did GetTupleTransactionInfo() use slot_getsysattr() originally? Was
it chosen to avoid full tuple conversion/detoasting overhead via
ExecFetchSlotHeapTuple(), and what is the performance trade-off of changing
it?

2. How is a "normal transaction ID" strictly defined here? Does the check
use TransactionIdIsNormal() to filter out frozen, bootstrap, and
bootstrap/subtransaction placeholder XIDs?

3. How can this bug be reliably reproduced in an automated regression test?
Is it possible to artificially freeze a tuple via vacuum or pg_freeze/SQL
commands and trigger a logical replication conflict deterministically, or
does it require specific timing with XID wraparound?

I'm kind of busy as my university exams are approaching but I'll try to
find a perfect time to review the patch.

With Regards,
Osama Abdul Qader.

>

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Bertrand Drouvot 2026-11-04 13:24:18 Re: Avoid resetting subscription two_phase state to pending when already enabled
Previous Message Zhijie Hou 2026-11-04 13:04:42 Avoid using raw xmin of frozen tuples in logicalrep conflict detection