From 79e50ccf5481cf35eb11ec39d2d3e9c79131d340 Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Wed, 4 Nov 2026 09:29:08 -0500 Subject: [PATCH 5/6] Advance oldestMulti before WAL-logging MultiXact truncation A checkpoint starting during truncation could record the old oldestMulti, which points at a removed segment after recovery. Reported-by: vignesh C Discussion: https://postgr.es/m/CALDaNm1+SiZ9SGt=X9KpOAx4vG8ZjrsXSq1o4nEiP7C_=NDGnA@mail.gmail.com Backpatch-through: 14 --- src/backend/access/transam/multixact.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index e75c5238295..55367a29896 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -2786,9 +2786,6 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB) Assert((MyProc->delayChkptFlags & DELAY_CHKPT_START) == 0); MyProc->delayChkptFlags |= DELAY_CHKPT_START; - /* WAL log truncation */ - WriteMTruncateXlogRec(newOldestMultiDB, newOldestMulti, newOldestOffset); - /* * Update in-memory limits before performing the truncation, while inside * the critical section: Have to do it before truncation, to prevent @@ -2796,6 +2793,12 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB) * section as otherwise a future call to this function would error out, * while looking up the oldest member in offsets, if our caller crashes * before updating the limits. + * + * Also do it before writing the WAL record. DELAY_CHKPT_START does not + * keep a checkpoint from setting its redo pointer and reading oldestMulti + * for the checkpoint record before it waits for us. If its redo pointer + * is after our record, recovery from it will not replay the record, so it + * must already see the new value. */ LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE); MultiXactState->oldestMultiXactId = newOldestMulti; @@ -2803,6 +2806,9 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB) MultiXactState->oldestOffset = newOldestOffset; LWLockRelease(MultiXactGenLock); + /* WAL log truncation */ + WriteMTruncateXlogRec(newOldestMultiDB, newOldestMulti, newOldestOffset); + /* First truncate members */ PerformMembersTruncation(newOldestOffset); -- 2.37.1 (Apple Git-137.1)