diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index 9568ff1..a12a281 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -348,6 +348,10 @@ static bool MultiXactOffsetPagePrecedes(int page1, int page2); static bool MultiXactMemberPagePrecedes(int page1, int page2); static bool MultiXactOffsetPrecedes(MultiXactOffset offset1, MultiXactOffset offset2); +static MultiXactId +MultiXactIdMostRecent(MultiXactId multi1, MultiXactId multi2); +static MultiXactId +find_earliest_multixact_on_disk(void); static void ExtendMultiXactOffset(MultiXactId multi); static void ExtendMultiXactMember(MultiXactOffset offset, int nmembers); static void DetermineSafeOldestOffset(MultiXactId oldestMXact); @@ -1958,10 +1962,17 @@ StartupMultiXact(void) MultiXactMemberCtl->shared->latest_page_number = pageno; /* - * compute the oldest member we need to keep around to avoid old member - * data overrun. + * Compute the oldest member we need to keep around to avoid old member + * data overrun. We scan the SLRU's physical files to find the earliest + * possible multixact that is stored on disk, and we use that if it's more + * recent than the oldestMultiXactId from shared memory, to avoid + * attempting to accessing a multixact that has already been truncated, + * during a replay (and also to handle a historical pg_upgrade bug that + * might have corrupted oldestMultiXactId in pg_control, leaving it + * pointing to a non-existant multixact). */ - DetermineSafeOldestOffset(MultiXactState->oldestMultiXactId); + DetermineSafeOldestOffset(MultiXactIdMostRecent(MultiXactState->oldestMultiXactId, + find_earliest_multixact_on_disk())); } /* @@ -2206,7 +2217,10 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid) * itself, but there's an important special case: if there are no * multixacts in existence at all, oldest_datminmxid obviously can't point * to one. It will instead point to the multixact ID that will be - * assigned the next time one is needed. + * assigned the next time one is needed. Note that oldest_datminmxid may + * refer to multixacts that have already been truncated in certain + * circumstances (pg_update corruption caused by a historical bug), so we + * need to use the earliest multixact found on disk if it is more recent. * * NB: oldest_dataminmxid is the oldest multixact that might still be * referenced from a table, unlike in DetermineSafeOldestOffset, where we @@ -2226,7 +2240,9 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid) else { LWLockRelease(MultiXactGenLock); - oldestOffset = find_multixact_start(oldest_datminmxid); + oldestOffset = + find_multixact_start(MultiXactIdMostRecent(oldest_datminmxid, + find_earliest_multixact_on_disk())); } /* Grab lock for just long enough to set the new limit values */ @@ -2802,7 +2818,6 @@ TruncateMultiXact(void) MultiXactId oldestMXact; MultiXactOffset oldestOffset; MultiXactOffset nextOffset; - mxtruncinfo trunc; MultiXactId earliest; MembersLiveRange range; @@ -2821,11 +2836,7 @@ TruncateMultiXact(void) * truncate the members SLRU. So we first scan the directory to determine * the earliest offsets page number that we can read without error. */ - trunc.earliestExistingPage = -1; - SlruScanDirectory(MultiXactOffsetCtl, SlruScanDirCbFindEarliest, &trunc); - earliest = trunc.earliestExistingPage * MULTIXACT_OFFSETS_PER_PAGE; - if (earliest < FirstMultiXactId) - earliest = FirstMultiXactId; + earliest = find_earliest_multixact_on_disk(); /* nothing to do */ if (MultiXactIdPrecedes(oldestMXact, earliest)) @@ -2869,6 +2880,36 @@ TruncateMultiXact(void) } /* + * Returns whichever of its arguments comes 'later'. + * Like 'Max', but using Xid-style modulo arithmetic. + */ +static MultiXactId +MultiXactIdMostRecent(MultiXactId multi1, MultiXactId multi2) +{ + return MultiXactIdPrecedes(multi1, multi2) ? multi2 : multi1; +} + +/* + * Scan the physical files in the offsets SLRU and return the earliest + * multixact ID that could be stored there. + */ +static MultiXactId +find_earliest_multixact_on_disk(void) +{ + MultiXactId earliest; + mxtruncinfo trunc; + + trunc.earliestExistingPage = -1; + SlruScanDirectory(MultiXactOffsetCtl, SlruScanDirCbFindEarliest, &trunc); + Assert(trunc.earliestExistingPage != -1); /* XXX: error, there must be at least one file */ + earliest = trunc.earliestExistingPage * MULTIXACT_OFFSETS_PER_PAGE; + if (earliest < FirstMultiXactId) + earliest = FirstMultiXactId; + + return earliest; +} + +/* * Decide which of two MultiXactOffset page numbers is "older" for truncation * purposes. *