| 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.
>
| 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 |