64-bit MultiXactOffset vs. 9.3->9.4 upgrade, pg_resetwal, "wraparound" msg
commit bd8d9c9 wrote:
Commit: Heikki Linnakangas <heikki.linnakangas@iki.fi>
CommitDate: Tue Dec 9 13:53:03 2025 +0200Widen MultiXactOffset to 64 bits
--- a/src/bin/pg_upgrade/pg_upgrade.c +++ b/src/bin/pg_upgrade/pg_upgrade.c
+ nxtmulti = old_cluster.controldata.chkpnt_nxtmulti; + if (old_cluster.controldata.cat_ver >= MULTIXACT_FORMATCHANGE_CAT_VER) + { + /* Versions 9.3 - 18: convert all multixids */ + oldstMulti = old_cluster.controldata.chkpnt_oldstMulti;
If a cluster's upgrade history includes an upgrade from 9.3 to early 9.4, it
may have a wrong value here. Specifically, upgrades done before a61daa14
(2014-07 commit) have that hazard. We still have backend code to detect such
cases and reduce damage:
ereport(LOG,
(errmsg("cannot truncate up to MultiXact %u because it does not exist on disk, skipping truncation",
newOldestMulti)));
However, the pg_upgrade side from the v19 commit lacks such protection. If
heap tuples still reference older multixacts than the faulty control data
suggests, pg_upgrade will copy too small a range, making affected tuples
unreadable.
I also had Opus 4.8 look for defects in this change and write test cases. It
didn't find the above problem, but it had other findings. I'm attaching the
full report. I recommend fixing at least these before release:
+| 5 | `resetwal-nextmxoff-zero` | `src/bin/pg_resetwal/pg_resetwal.c:703`, and `-O` with no zero check at 297-307 | `pg_resetwal -f` (or `-O 0`) leaves `nextMultiOffset = 0`, the reserved "invalid offset"; first multixact created afterwards is permanently unreadable: `ERROR: MultiXact 1 has invalid offset` | Yes — `src/test/modules/test_slru/t/003_multixact_offset.pl` |
+| 7 | `members-truncation-apparent-wraparound` | `src/backend/access/transam/multixact.c:2642-2647` | When `nextOffset` lands on a members page boundary, truncation logs `could not truncate directory "pg_multixact/members": apparent wraparound` (impossible for a 64-bit counter) and reclaims nothing | Yes — `003_multixact_offset.pl` |
Others are more optional or already reported. In particular, two other
findings were already reported and apparently fixed after the Opus run, in
thread "pg_upgrade silently truncates nextMultiOffset to 32 bits":
/messages/by-id/CAD21AoCvzerscfU8o4ARQ793yAGHpQ72r2x5apeC_W2-k=SLCQ@mail.gmail.com
Attachments:
bd8d9c9-MultiXactOffset-tests-v0.patchtext/plain; charset=utf-8Download+853-1
On 28/08/2026 02:17, Noah Misch wrote:
commit bd8d9c9 wrote:
Commit: Heikki Linnakangas <heikki.linnakangas@iki.fi>
CommitDate: Tue Dec 9 13:53:03 2025 +0200Widen MultiXactOffset to 64 bits
--- a/src/bin/pg_upgrade/pg_upgrade.c +++ b/src/bin/pg_upgrade/pg_upgrade.c+ nxtmulti = old_cluster.controldata.chkpnt_nxtmulti; + if (old_cluster.controldata.cat_ver >= MULTIXACT_FORMATCHANGE_CAT_VER) + { + /* Versions 9.3 - 18: convert all multixids */ + oldstMulti = old_cluster.controldata.chkpnt_oldstMulti;If a cluster's upgrade history includes an upgrade from 9.3 to early 9.4, it
may have a wrong value here. Specifically, upgrades done before a61daa14
(2014-07 commit) have that hazard. We still have backend code to detect such
cases and reduce damage:ereport(LOG,
(errmsg("cannot truncate up to MultiXact %u because it does not exist on disk, skipping truncation",
newOldestMulti)));However, the pg_upgrade side from the v19 commit lacks such protection. If
heap tuples still reference older multixacts than the faulty control data
suggests, pg_upgrade will copy too small a range, making affected tuples
unreadable.
Thanks, I'll look into this. My first reaction is that I think if
oldstMulti is incorrectly too old, the upgrade will fail because the
conversion routine will fail to find it. If it's too new, i.e. "in the
future", it will also fail to find it.
The third possibility is that the bogus oldstMulti value is within the
range of the "real" range. That can happen if multixid wraparound had
already happened before the (broken) 9.3 -> 9.4 upgrade. In that case,
even if the multixids are still readable on disk, you're one vacuum away
from truncating them. In other words, the damage has already been done,
or could be done at any minute. That's the scenario for which the commit
message 78db307bb2 says "this mechanism cannot save us".
But I'll do some testing of that. Let's ensure that the error message
makes sense at the very least.
I also had Opus 4.8 look for defects in this change and write test cases. It
didn't find the above problem, but it had other findings. I'm attaching the
full report. I recommend fixing at least these before release:+| 5 | `resetwal-nextmxoff-zero` | `src/bin/pg_resetwal/pg_resetwal.c:703`, and `-O` with no zero check at 297-307 | `pg_resetwal -f` (or `-O 0`) leaves `nextMultiOffset = 0`, the reserved "invalid offset"; first multixact created afterwards is permanently unreadable: `ERROR: MultiXact 1 has invalid offset` | Yes — `src/test/modules/test_slru/t/003_multixact_offset.pl` |
This was already fixed in commit 2cad308cb8.
+| 7 | `members-truncation-apparent-wraparound` | `src/backend/access/transam/multixact.c:2642-2647` | When `nextOffset` lands on a members page boundary, truncation logs `could not truncate directory "pg_multixact/members": apparent wraparound` (impossible for a 64-bit counter) and reclaims nothing | Yes — `003_multixact_offset.pl` |
Ok, I'll look into this one, and the other ones that might not be fixed yet.
- Heikki