Hi everyone, On Wed, 4 Nov, 2026, 6:35 pm Zhijie Hou, <[email protected]> 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. >
