| From: | Zhijie Hou <houzhijie22(at)gmail(dot)com> |
|---|---|
| To: | Andres Freund <andres(at)anarazel(dot)de> |
| 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-06 04:13:11 |
| Message-ID: | 657b0988-778a-4ad8-8f64-67d43b770baa@gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi Andres,
On Wed, Nov 4, 2026 at 09:33 PM Andres Freund wrote:
> On 2026-11-04 21:04:42 +0800, Zhijie Hou wrote:
>> 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.
>
> That surely is not the right fix, it breaks any sense of abstraction across
> table AMs. There's afaict no guarantee that the target table is heapam. And
> even if there currently is, we shouldn't bake that in further.
Thanks for pointing it out. I agree that conflict.c shouldn't be
reaching into BufferHeapTupleTableSlot or HeapTupleHeaderGetXmin() directly.
To avoid that, on master, it seems cleanest to handle this at the
TupleTableSlotOps level (alongside getsysattr and is_current_xact_tuple
in execTuples.c), for example, via an optional callback that returns the
effective xmin (HeapTupleHeaderGetXmin() for heap/buffer-heap slots, and
InvalidTransactionId when NULL for virtual/minimal slots or AMs that
don't store tuple xmin).
TransactionId (*get_effective_xmin) (TupleTableSlot *slot);
I also considered adding a callback to TableAmRoutine instead, but
wasn't sure if it's worth adding a TableAmRoutine entry when the tuple
is already in the slot and getsysattr / is_current_xact_tuple are on
TupleTableSlotOps.
For back-patching to 18/19 where we can't change TupleTableSlotOps
without breaking ABI, GetTupleTransactionInfo() can branch on
TTS_IS_BUFFERTUPLE(localslot) || TTS_IS_HEAPTUPLE(localslot) to use
HeapTupleHeaderGetXmin() and fall back to slot_getsysattr() otherwise,
while still filtering out !TransactionIdIsNormal(xmin).
What do you think ?
--
Best Regards,
Zhijie Hou
| From | Date | Subject | |
|---|---|---|---|
| Previous Message | Narek Galstyan | 2026-11-06 03:40:40 | Bug report and fix for: COMMIT/ROLLBACK AND CHAIN after an error drops the isolation level |