Remove some unnecessary code from multixact truncation
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Thu, 15 Jan 2026 11:34:50 +0000 (13:34 +0200)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Thu, 15 Jan 2026 11:34:50 +0000 (13:34 +0200)
With 64-bit multixact offsets, PerformMembersTruncation() doesn't need
the starting offset anymore. The 'oldestOffset' value that
TruncateMultiXact() calculates is no longer used for anything. Remove
it, and the code to calculate it.

'oldestOffset' was included in the WAL record as 'startTruncMemb',
which sounds nice if you e.g. look at the WAL with pg_waldump, but it
was also confusing because we didn't actually use the value for
determining what to truncate. Replaying the WAL would remove all
segments older than 'endTruncMemb', regardless of
'startTruncMemb'. The 'startTruncOff' stored in the WAL record was
similarly unnecessary even before 64-bit multixid offsets, it was
stored just for the sake of symmetry with 'startTruncMemb'. Remove
both from the WAL record, and rename the remaining 'endTruncOff' to
'oldestMulti' and 'endTruncMemb' to 'oldestOffset', for consistency
with the variable names used for them in other places.

Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru>
Discussion: https://www.postgresql.org/message-id/000301b2-5b81-4938-bdac-90f6eb660843@iki.fi

src/backend/access/rmgrdesc/mxactdesc.c
src/backend/access/transam/multixact.c
src/include/access/multixact.h
src/include/access/xlog_internal.h

index 32a031d9e603f97575016af36bcc9312f431bffc..6919c7ddd2ea33bc817c37f9c7ccb8138330f2cd 100644 (file)
@@ -74,9 +74,8 @@ multixact_desc(StringInfo buf, XLogReaderState *record)
        {
                xl_multixact_truncate *xlrec = (xl_multixact_truncate *) rec;
 
-               appendStringInfo(buf, "offsets [%u, %u), members [%" PRIu64 ", %" PRIu64 ")",
-                                                xlrec->startTruncOff, xlrec->endTruncOff,
-                                                xlrec->startTruncMemb, xlrec->endTruncMemb);
+               appendStringInfo(buf, "oldestMulti %u, oldestOffset %" PRIu64,
+                                                xlrec->oldestMulti, xlrec->oldestOffset);
        }
 }
 
index 3f423636b489340ff8b52e457d506a3dc5dd4e1e..3528692881578ec09c5002a46ae9beda6ed84ea6 100644 (file)
@@ -282,9 +282,7 @@ static void ExtendMultiXactMember(MultiXactOffset offset, int nmembers);
 static void SetOldestOffset(void);
 static bool find_multixact_start(MultiXactId multi, MultiXactOffset *result);
 static void WriteMTruncateXlogRec(Oid oldestMultiDB,
-                                                                 MultiXactId startTruncOff,
                                                                  MultiXactId endTruncOff,
-                                                                 MultiXactOffset startTruncMemb,
                                                                  MultiXactOffset endTruncMemb);
 
 
@@ -2558,45 +2556,22 @@ MultiXactMemberFreezeThreshold(void)
        return Min(result, autovacuum_multixact_freeze_max_age);
 }
 
-typedef struct mxtruncinfo
-{
-       int64           earliestExistingPage;
-} mxtruncinfo;
 
 /*
- * SlruScanDirectory callback
- *             This callback determines the earliest existing page number.
- */
-static bool
-SlruScanDirCbFindEarliest(SlruCtl ctl, char *filename, int64 segpage, void *data)
-{
-       mxtruncinfo *trunc = (mxtruncinfo *) data;
-
-       if (trunc->earliestExistingPage == -1 ||
-               ctl->PagePrecedes(segpage, trunc->earliestExistingPage))
-       {
-               trunc->earliestExistingPage = segpage;
-       }
-
-       return false;                           /* keep going */
-}
-
-
-/*
- * Delete members segments [oldest, newOldest)
+ * Delete members segments older than newOldestOffset
  */
 static void
-PerformMembersTruncation(MultiXactOffset oldestOffset, MultiXactOffset newOldestOffset)
+PerformMembersTruncation(MultiXactOffset newOldestOffset)
 {
        SimpleLruTruncate(MultiXactMemberCtl,
                                          MXOffsetToMemberPage(newOldestOffset));
 }
 
 /*
- * Delete offsets segments [oldest, newOldest)
+ * Delete offsets segments older than newOldestMulti
  */
 static void
-PerformOffsetsTruncation(MultiXactId oldestMulti, MultiXactId newOldestMulti)
+PerformOffsetsTruncation(MultiXactId newOldestMulti)
 {
        /*
         * We step back one multixact to avoid passing a cutoff page that hasn't
@@ -2626,10 +2601,7 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
        MultiXactId oldestMulti;
        MultiXactId nextMulti;
        MultiXactOffset newOldestOffset;
-       MultiXactOffset oldestOffset;
        MultiXactOffset nextOffset;
-       mxtruncinfo trunc;
-       MultiXactId earliest;
 
        Assert(!RecoveryInProgress());
        Assert(MultiXactState->finishedStartup);
@@ -2661,61 +2633,8 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
        }
 
        /*
-        * Note we can't just plow ahead with the truncation; it's possible that
-        * there are no segments to truncate, which is a problem because we are
-        * going to attempt to read the offsets page to determine where to
-        * truncate the members SLRU.  So we first scan the directory to determine
-        * the earliest offsets page number that we can read without error.
-        *
-        * When nextMXact is less than one segment away from multiWrapLimit,
-        * SlruScanDirCbFindEarliest can find some early segment other than the
-        * actual earliest.  (MultiXactOffsetPagePrecedes(EARLIEST, LATEST)
-        * returns false, because not all pairs of entries have the same answer.)
-        * That can also arise when an earlier truncation attempt failed unlink()
-        * or returned early from this function.  The only consequence is
-        * returning early, which wastes space that we could have liberated.
-        *
-        * NB: It's also possible that the page that oldestMulti is on has already
-        * been truncated away, and we crashed before updating oldestMulti.
-        */
-       trunc.earliestExistingPage = -1;
-       SlruScanDirectory(MultiXactOffsetCtl, SlruScanDirCbFindEarliest, &trunc);
-       earliest = trunc.earliestExistingPage * MULTIXACT_OFFSETS_PER_PAGE;
-       if (earliest < FirstMultiXactId)
-               earliest = FirstMultiXactId;
-
-       /* If there's nothing to remove, we can bail out early. */
-       if (MultiXactIdPrecedes(oldestMulti, earliest))
-       {
-               LWLockRelease(MultiXactTruncationLock);
-               return;
-       }
-
-       /*
-        * First, compute the safe truncation point for MultiXactMember. This is
-        * the starting offset of the oldest multixact.
-        *
-        * Hopefully, find_multixact_start will always work here, because we've
-        * already checked that it doesn't precede the earliest MultiXact on disk.
-        * But if it fails, don't truncate anything, and log a message.
-        */
-       if (oldestMulti == nextMulti)
-       {
-               /* there are NO MultiXacts */
-               oldestOffset = nextOffset;
-       }
-       else if (!find_multixact_start(oldestMulti, &oldestOffset))
-       {
-               ereport(LOG,
-                               (errmsg("oldest MultiXact %u not found, earliest MultiXact %u, skipping truncation",
-                                               oldestMulti, earliest)));
-               LWLockRelease(MultiXactTruncationLock);
-               return;
-       }
-
-       /*
-        * Secondly compute up to where to truncate. Lookup the corresponding
-        * member offset for newOldestMulti for that.
+        * Compute up to where to truncate MultiXactMember. Lookup the
+        * corresponding member offset for newOldestMulti for that.
         */
        if (newOldestMulti == nextMulti)
        {
@@ -2732,13 +2651,11 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
        }
 
        elog(DEBUG1, "performing multixact truncation: "
-                "offsets [%u, %u), offsets segments [%" PRIx64 ", %" PRIx64 "), "
-                "members [%" PRIu64 ", %" PRIu64 "), members segments [%" PRIx64 ", %" PRIx64 ")",
-                oldestMulti, newOldestMulti,
-                MultiXactIdToOffsetSegment(oldestMulti),
+                "oldestMulti %u (offsets segment %" PRIx64 "), "
+                "oldestOffset %" PRIu64 " (members segment %" PRIx64 ")",
+                newOldestMulti,
                 MultiXactIdToOffsetSegment(newOldestMulti),
-                oldestOffset, newOldestOffset,
-                MXOffsetToMemberSegment(oldestOffset),
+                newOldestOffset,
                 MXOffsetToMemberSegment(newOldestOffset));
 
        /*
@@ -2759,9 +2676,7 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
        MyProc->delayChkptFlags |= DELAY_CHKPT_START;
 
        /* WAL log truncation */
-       WriteMTruncateXlogRec(newOldestMultiDB,
-                                                 oldestMulti, newOldestMulti,
-                                                 oldestOffset, newOldestOffset);
+       WriteMTruncateXlogRec(newOldestMultiDB, newOldestMulti, newOldestOffset);
 
        /*
         * Update in-memory limits before performing the truncation, while inside
@@ -2778,10 +2693,10 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
        LWLockRelease(MultiXactGenLock);
 
        /* First truncate members */
-       PerformMembersTruncation(oldestOffset, newOldestOffset);
+       PerformMembersTruncation(newOldestOffset);
 
        /* Then offsets */
-       PerformOffsetsTruncation(oldestMulti, newOldestMulti);
+       PerformOffsetsTruncation(newOldestMulti);
 
        MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 
@@ -2860,19 +2775,15 @@ MultiXactIdPrecedesOrEquals(MultiXactId multi1, MultiXactId multi2)
  */
 static void
 WriteMTruncateXlogRec(Oid oldestMultiDB,
-                                         MultiXactId startTruncOff, MultiXactId endTruncOff,
-                                         MultiXactOffset startTruncMemb, MultiXactOffset endTruncMemb)
+                                         MultiXactId oldestMulti,
+                                         MultiXactOffset oldestOffset)
 {
        XLogRecPtr      recptr;
        xl_multixact_truncate xlrec;
 
        xlrec.oldestMultiDB = oldestMultiDB;
-
-       xlrec.startTruncOff = startTruncOff;
-       xlrec.endTruncOff = endTruncOff;
-
-       xlrec.startTruncMemb = startTruncMemb;
-       xlrec.endTruncMemb = endTruncMemb;
+       xlrec.oldestMulti = oldestMulti;
+       xlrec.oldestOffset = oldestOffset;
 
        XLogBeginInsert();
        XLogRegisterData(&xlrec, SizeOfMultiXactTruncate);
@@ -2943,14 +2854,12 @@ multixact_redo(XLogReaderState *record)
                           SizeOfMultiXactTruncate);
 
                elog(DEBUG1, "replaying multixact truncation: "
-                        "offsets [%u, %u), offsets segments [%" PRIx64 ", %" PRIx64 "), "
-                        "members [%" PRIu64 ", %" PRIu64 "), members segments [%" PRIx64 ", %" PRIx64 ")",
-                        xlrec.startTruncOff, xlrec.endTruncOff,
-                        MultiXactIdToOffsetSegment(xlrec.startTruncOff),
-                        MultiXactIdToOffsetSegment(xlrec.endTruncOff),
-                        xlrec.startTruncMemb, xlrec.endTruncMemb,
-                        MXOffsetToMemberSegment(xlrec.startTruncMemb),
-                        MXOffsetToMemberSegment(xlrec.endTruncMemb));
+                        "oldestMulti %u (offsets segment %" PRIx64 "), "
+                        "oldestOffset %" PRIu64 " (members segment %" PRIx64 ")",
+                        xlrec.oldestMulti,
+                        MultiXactIdToOffsetSegment(xlrec.oldestMulti),
+                        xlrec.oldestOffset,
+                        MXOffsetToMemberSegment(xlrec.oldestOffset));
 
                /* should not be required, but more than cheap enough */
                LWLockAcquire(MultiXactTruncationLock, LW_EXCLUSIVE);
@@ -2959,19 +2868,19 @@ multixact_redo(XLogReaderState *record)
                 * Advance the horizon values, so they're current at the end of
                 * recovery.
                 */
-               SetMultiXactIdLimit(xlrec.endTruncOff, xlrec.oldestMultiDB);
+               SetMultiXactIdLimit(xlrec.oldestMulti, xlrec.oldestMultiDB);
 
-               PerformMembersTruncation(xlrec.startTruncMemb, xlrec.endTruncMemb);
+               PerformMembersTruncation(xlrec.oldestOffset);
 
                /*
                 * During XLOG replay, latest_page_number isn't necessarily set up
                 * yet; insert a suitable value to bypass the sanity test in
                 * SimpleLruTruncate.
                 */
-               pageno = MultiXactIdToOffsetPage(xlrec.endTruncOff);
+               pageno = MultiXactIdToOffsetPage(xlrec.oldestMulti);
                pg_atomic_write_u64(&MultiXactOffsetCtl->shared->latest_page_number,
                                                        pageno);
-               PerformOffsetsTruncation(xlrec.startTruncOff, xlrec.endTruncOff);
+               PerformOffsetsTruncation(xlrec.oldestMulti);
 
                LWLockRelease(MultiXactTruncationLock);
        }
index 26f6bd2890ef419b5ef4f045a02205110302a49d..2ae8b571dcc0f88127c73023a2524279930ae881 100644 (file)
@@ -83,13 +83,11 @@ typedef struct xl_multixact_truncate
 {
        Oid                     oldestMultiDB;
 
-       /* to-be-truncated range of multixact offsets */
-       MultiXactId startTruncOff;      /* just for completeness' sake */
-       MultiXactId endTruncOff;
+       /* truncate multixact offsets older than this */
+       MultiXactId oldestMulti;
 
-       /* to-be-truncated range of multixact members */
-       MultiXactOffset startTruncMemb;
-       MultiXactOffset endTruncMemb;
+       /* truncate multixact members older than this */
+       MultiXactOffset oldestOffset;
 } xl_multixact_truncate;
 
 #define SizeOfMultiXactTruncate (sizeof(xl_multixact_truncate))
index 79e5cb45f6d1d5b45c69eb4117f4df097ee1d788..fbc5d77ab39c367560c041fabb0f6222e017fb9e 100644 (file)
@@ -31,7 +31,7 @@
 /*
  * Each page of XLOG file has a header like this:
  */
-#define XLOG_PAGE_MAGIC 0xD11A /* can be used as WAL version indicator */
+#define XLOG_PAGE_MAGIC 0xD11B /* can be used as WAL version indicator */
 
 typedef struct XLogPageHeaderData
 {