Re: An improvement of ProcessTwoPhaseBuffer logic
Noah Misch <noah@leadboat.com>
From: Noah Misch <noah@leadboat.com>
To: Michael Paquier <michael@paquier.xyz>
Cc: Vitaly Davydov <v.davydov@postgrespro.ru>, pgsql-hackers <pgsql-hackers@postgresql.org>
Date: 2025-01-16T20:52:54Z
Lists: pgsql-hackers
Commits
Same data as JSON:
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Integrate FullTransactionIds deeper into two-phase code
- 62a17a92833d 19 (unreleased) landed
-
Merge copies of converting an XID to a FullTransactionId.
- 1587f7b9fc1a 17.3 landed
- 81772a495ec9 18.0 landed
-
Revert recent changes related to handling of 2PC files at recovery
- 4d6d7bdc0642 13.19 landed
- 060f9f5ea537 14.16 landed
- 42c900d31e57 15.11 landed
- 4d72357c40ee 16.7 landed
- d1bf86a62211 17.3 landed
- a6c70f68cdeb 18.0 landed
-
Fix failures with incorrect epoch handling for 2PC files at recovery
- c3de0f9eed38 17.3 landed
- 7e125b20eed6 18.0 landed
Attachments
- FullTransactionIdFromCurrentEpoch-v0.1.patch (text/plain) patch v0
On Thu, Jan 16, 2025 at 04:50:09PM +0900, Michael Paquier wrote:
> On Wed, Jan 15, 2025 at 05:00:51PM -0800, Noah Misch wrote:
> > I think "using the current epoch" is wrong for half of the nextFullXid values
> > having epoch > 0. For example, nextFullId==2^32 is in epoch 1, but all the
> > allowable XIDs are in epoch 0. (I mean "allowable" in the sense of
> > AssertTransactionIdInAllowableRange().) From then until we assign another
> > 2^31 XIDs, epochs 0 and 1 are both expected in XID values. After 2^31 XID
> > assignments, every allowable XID be in epoch 1. Hence, twophase.c would need
> > two-epoch logic like we have in widen_snapshot_xid() and XLogRecGetFullXid().
> > Is that right? (I wrote this in a hurry, so this email may have more than the
> > standard level of errors.) Before commit 7e125b2, twophase also had that
> > logic. I didn't work out the user-visible consequences of that logic's new
> > absence here, but I bet on twophase recovery breakage. Similar problem here
> > (up to two epochs are acceptable, not just one):
> >
> > + /* Discard files from past epochs */
> > + if (EpochFromFullTransactionId(fxid) < EpochFromFullTransactionId(nextXid))
>
> Oops, you're right. Your suggestion to unify all that in a single
> routine is an excellent idea. Missed the bits in xid8funcs.c.
Added.
> > I wrote the attached half-baked patch to fix those, but I tend to think it's
> > better to use FullTransactionId as many places as possible in twophase.c.
> > (We'd still need to convert XIDs that we read from xl_xact_prepare records,
> > along the lines of XLogRecGetFullXid().) How do you see it?
>
> I'm all for integrating more FullTransactionIds now that these reflect
> in the file names, and do a deeper cut.
>
> As far as I understand, the most important point of the logic is to
> detect and discard the future files first in restoreTwoPhaseData() ->
> ProcessTwoPhaseBuffer() when scanning the contents of pg_twophase at
> the beginning of recovery. Once this filtering is done, it should be
> safe to use your FullTransactionIdFromAllowableAt() when doing
> the fxid <-> xid transitions between the records and the files on disk
> flushed by a restartpoint which store an XID, and the shmem state of
> GlobalTransactionData with a fxid.
(I did not expect that a function called restoreTwoPhaseData() would run before
a function called PrescanPreparedTransactions(), but so it is.)
How is it that restoreTwoPhaseData() -> ProcessTwoPhaseBuffer() can safely
call TransactionIdDidAbort() when we've not replayed WAL to make CLOG
consistent? What can it assume about the value of TransamVariables->nextXid
at that early time?
> With the additions attached, FullTransactionIdFromAllowableAt() gets
> down from 8 to 6 calls in twophase.c. The change related to
> MarkAsPreparingGuts() seems optional, though.
Thanks. It's probably not worth doing at that level of reduction. I tried
spreading fxid further and got it down to three conversions, corresponding to
redo of each of XLOG_XACT_PREPARE, XLOG_XACT_COMMIT_PREPARED, and
XLOG_XACT_ABORT_PREPARED. I'm attaching the WIP of that. It's not as
satisfying as I expected, so FullTransactionIdFromCurrentEpoch-v0.patch may
yet be the better direction.
The ProcessTwoPhaseBuffer() code to remove "past two-phase state" seems
best-effort, independent of this change. Just because an XID is in a
potentially-acceptable epoch doesn't mean TransactionIdDidCommit() will find
clog for it. I've not studied whether that matters.
Incidentally, this comment about when a function is called:
* PrescanPreparedTransactions
*
* Scan the shared memory entries of TwoPhaseState and determine the range
* of valid XIDs present. This is run during database startup, after we
* have completed reading WAL. TransamVariables->nextXid has been set to
* one more than the highest XID for which evidence exists in WAL.
doesn't match the timing of the actual earliest call:
/* REDO */
if (InRecovery)
{
...
if (ArchiveRecoveryRequested && EnableHotStandby)
{
...
if (wasShutdown)
oldestActiveXID = PrescanPreparedTransactions(&xids, &nxids);
else
oldestActiveXID = checkPoint.oldestActiveXid;
...
PerformWalRecovery();
performedWalRecovery = true;
> I am trying to figure
> out how to write a regression test to trigger this error, lacking a
> bit of time today. That's going to require more trickery with
> pg_resetwal to make that cheap, or something like that.. Attached are
> some suggestions, as of a 0002 that applies on top of your 0001.
Thanks. I'd value having your regression test, but manual-ish testing could
suffice if it's too hard.
> XLogRecGetFullXid() is used nowhere. This could be removed, perhaps,
> or not?
Maybe. Looks like it was born unused in 67b9b3c (2019-07), so removing may as
well be a separate discussion.