From fb0fa652780dda22f0f763ff7e176eff1d6943f4 Mon Sep 17 00:00:00 2001 From: Naga Appani Date: Sat, 19 Sep 2026 01:45:42 +0000 Subject: [PATCH v1] Maintain MultiXact oldestOffset during recovery MultiXactState->oldestOffset was only updated outside recovery, so on a hot standby it stayed at 0 for the life of the server. As a result pg_get_multixact_stats() computed num_members as nextOffset - 0 and over-reported the retained member count (and members_size), contradicting the same query on the primary for identical data. Update oldestOffset from the WAL record while replaying XLOG_MULTIXACT_TRUNCATE_ID, so the value is kept current during recovery. TrimMultiXact() still recomputes a fresh value at end of recovery. Suggested-by: Heikki Linnakangas Discussion: https://www.postgresql.org/message-id/05d064e8-219d-40bd-9fdf-652b648ba317%40iki.fi --- src/backend/access/transam/multixact.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index 424516d..0019228 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -2989,6 +2989,15 @@ multixact_redo(XLogReaderState *record) PerformMembersTruncation(xlrec.oldestOffset); PerformOffsetsTruncation(xlrec.oldestMulti); + /* + * Keep oldestOffset current during recovery too. It is not + * otherwise maintained until end of recovery, which would make + * pg_get_multixact_stats() over-report members on a hot standby. + */ + LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE); + MultiXactState->oldestOffset = xlrec.oldestOffset; + LWLockRelease(MultiXactGenLock); + LWLockRelease(MultiXactTruncationLock); } else -- 2.50.1