On 16/03/2026 18:09, Ayush Tiwari wrote:
The Issue:
In TruncateMultiXact(), we write the truncation WAL record
(WriteMTruncateXlogRec) before we actually perform the truncation via
PerformOffsetsTruncation() -> SimpleLruTruncate().
The problem arises from the "apparent wraparound" safety check inside
SimpleLruTruncate(). If SlruScanDirectory() detects an apparent
wraparound, SimpleLruTruncate() safely bails out and skips unlinking the
SLRU segments on the primary, logging: could not truncate directory
"%s": apparent wraparound.
However, the WAL record for the truncation has already been flushed.
Standbys replay this TRUNCATE_ID WAL record and blindly delete their
SLRU segments. At this point, the primary and standby have diverged.
Replaying the record will perform the same sanity checks against
wraparound as the primary does.
Hmm, although why did I not apply commit 817f74600d to 'master', only
backbranches? The bug that it fixed was related to minor version
upgrade, and thus it was not needed on 'master', but the code change
would nevertheless make a lot of sense on 'master' too.
The Impact:
If the standby is subsequently promoted to primary, any attempt to
access rows holding those older MultiXact IDs (which the original
primary decided to keep) will throw a FATAL: could not access status of
transaction error, effectively resulting in data loss / inaccessible
rows for the user.
Have you been able to reproduce that?
While the recent commits address the immediate standby crash involving
latest_page_number during multixact_redo(), they don't seem to prevent
the primary from emitting a "false" WAL truncation record when it
abandons its own truncation.
Proposed Approach:
It seems safer to only emit the WAL record if we are guaranteed to
follow through with the truncation. We could modify SimpleLruTruncate()
to perform its safety checks first and return a boolean indicating
whether the truncation is safe to proceed. TruncateMultiXact() would
then only call WriteMTruncateXlogRec() and proceed with physical
deletion if the check passes.
I have attached a rough draft patch illustrating this sequence change.
I agree that would probably be better. I'm not sure how straightforward
it will be to implement though, I wouldn't want to add much extra code
just for this.
P.S. Thanks for looking into this! This is hairy stuff, more review is
much appreciated.
- Heikki