Re: BUG #19760: CLUSTER loses a row the transaction inserted and then updated in a rolled-back subtransaction

From: Zhijie Hou <houzhijie22(at)gmail(dot)com>
To: Manu <manuelreyesbravo(at)gmail(dot)com>
Cc: Michael Paquier <michael(at)paquier(dot)xyz>, shihao zhong <zhong950419(at)gmail(dot)com>, Takeshi Ideriha <iderihatakeshi(at)gmail(dot)com>, Ayush Tiwari <ayushtiwari(dot)slg01(at)gmail(dot)com>, alpkeles99(at)gmail(dot)com, pgsql-bugs(at)lists(dot)postgresql(dot)org
Subject: Re: BUG #19760: CLUSTER loses a row the transaction inserted and then updated in a rolled-back subtransaction
Date: 2026-10-25 08:31:57
Message-ID: CAFvd2n-pPQvqTfbGKArgEKUfi3fAHq56EVpy6xXByx-SUKHn_g@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

Hi,

On Fri, Oct 23, 2026 at 4:44 AM Manu <manuelreyesbravo(at)gmail(dot)com> wrote:
>
> On Thu, Oct 22, 2026 at 11:22:08AM +0900, Michael Paquier wrote:
> > The DELETE case is a much more common case. It regresses even more
> > badly, and that does not look acceptable to me. That should be
> > optimizable, of course, I don't really know yet how to do that.
>
>
> For subxact_updates, nocfbot-0002 on top of it. In that branch xmin
> is in progress, so xmax is the inserter's xid or one of its
> subtransactions, and RecordTransactionAbort() writes a subtransaction
> abort to the clog before the xids leave the proc array. So
> TransactionIdDidAbort(xmax) gives the same answer there as
> !TransactionIdIsInProgress() && !TransactionIdDidCommit(), with one
> clog lookup and no proc array or pg_subtrans access.

This doesn't look safe to me. Even if TransactionIdDidAbort() returns true, the
xmax may not have been removed from PGPROC yet. So this change would create the
risky case where the old tuple has been marked as XMAX_INVAL while the new
tuple's xmin is still considered in progress.

After some testing, I confirmed that this causes an issue with HOT updates:

When a tuple is HOT-updated in a subtransaction and the transaction then rolls
back, there is a window after the XIDs are marked aborted in CLOG but before
they are removed from ProcArray. If a concurrent ANALYZE scans the table during
that window, HeapTupleSatisfiesVacuumHorizon() marks the old tuple as
HEAP_XMAX_INVALID while still treating the new heap-only tuple as in progress.
If brin_summarize_range() then scans the page in that same window,
heap_get_root_tuples() does not follow the HOT chain from the old tuple because
HeapTupleHeaderIsHotUpdated() returns false once HEAP_XMAX_INVALID is set, while
the scan still tries to index the in-progress heap-only tuple and fails with:

ERROR: failed to find parent tuple for heap-only tuple at xx in table
"mxact_abort"

See the attached follow-up patch, which reproduces this. This may not be a
common case, but it looks risky to create such a race.

Best Regards,
Zhijie Hou

Attachment Content-Type Size
nocfbot-test-clog-abort-race.patch application/octet-stream 2.6 KB

In response to

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message Ludwig Kannicht 2026-10-25 09:34:52 ORCE ROW LEVEL SECURITY makes ADD/VALIDATE FOREIGN KEY skip rows hidden from the owner (RI_Initial_Check)
Previous Message PG Bug reporting form 2026-10-25 07:00:02 BUG #19796: io_uring crashes with specific kernel/liburing versions + io_max_concurrency due to rings overlap