*/
MultiXactId oldestMultiXactId;
Oid oldestMultiXactDB;
+ MultiXactOffset oldestOffset;
/*
* This is what the previous checkpoint stored as the truncate position.
* against catastrophic data loss due to multixact wraparound. The basic
* rules are:
*
- * If we're past multiVacLimit, start trying to force autovacuum cycles.
+ * If we're past multiVacLimit or the safe threshold for member storage space,
+ * start trying to force autovacuum cycles.
* If we're past multiWarnLimit, start issuing warnings.
* If we're past multiStopLimit, refuse to create new MultiXactIds.
*
* Note these are pretty much the same protections in GetNewTransactionId.
*----------
*/
- if (!MultiXactIdPrecedes(result, MultiXactState->multiVacLimit))
+ if (!MultiXactIdPrecedes(result, MultiXactState->multiVacLimit) ||
+ (MultiXactState->nextOffset - MultiXactState->oldestOffset
+ > MULTIXACT_MEMBER_SAFE_THRESHOLD))
{
/*
* For safety's sake, we release MultiXactGenLock while sending
MultiXactId multiStopLimit;
MultiXactId multiWrapLimit;
MultiXactId curMulti;
+ MultiXactOffset oldestOffset;
+ MultiXactOffset nextOffset;
Assert(MultiXactIdIsValid(oldest_datminmxid));
if (multiVacLimit < FirstMultiXactId)
multiVacLimit += FirstMultiXactId;
+ /*
+ * Determine the offset of the oldest multixact that might still be
+ * referenced. Normally, we can read the offset from the multixact 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.
+ *
+ * NB: oldest_dataminmxid is the oldest multixact that might still be
+ * referenced from a table, unlike in DetermineSafeOldestOffset, where we
+ * do this same computation based on the oldest value that might still
+ * exist in the SLRU. This is because here we're trying to compute a
+ * threshold for activating autovacuum, which can only remove references
+ * to multixacts, whereas there we are computing a threshold for creating
+ * new multixacts, which requires the old ones to have first been
+ * truncated away by a checkpoint.
+ */
+ LWLockAcquire(MultiXactGenLock, LW_SHARED);
+ if (MultiXactState->nextMXact == oldest_datminmxid)
+ {
+ oldestOffset = MultiXactState->nextOffset;
+ LWLockRelease(MultiXactGenLock);
+ }
+ else
+ {
+ LWLockRelease(MultiXactGenLock);
+ oldestOffset = find_multixact_start(oldest_datminmxid);
+ }
+
/* Grab lock for just long enough to set the new limit values */
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
MultiXactState->oldestMultiXactId = oldest_datminmxid;
MultiXactState->multiWarnLimit = multiWarnLimit;
MultiXactState->multiStopLimit = multiStopLimit;
MultiXactState->multiWrapLimit = multiWrapLimit;
+ MultiXactState->oldestOffset = oldestOffset;
curMulti = MultiXactState->nextMXact;
+ nextOffset = MultiXactState->nextOffset;
LWLockRelease(MultiXactGenLock);
/* Log the info */
* database, it'll call here, and we'll signal the postmaster to start
* another iteration immediately if there are still any old databases.
*/
- if (MultiXactIdPrecedes(multiVacLimit, curMulti) &&
+ if ((MultiXactIdPrecedes(multiVacLimit, curMulti) ||
+ (nextOffset - oldestOffset > MULTIXACT_MEMBER_SAFE_THRESHOLD)) &&
IsUnderPostmaster && !InRecovery)
SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
MultiXactOffset oldestOffset;
/*
- * We determine the safe upper bound for offsets of new xacts by reading
- * the offset of the oldest multixact, and going back one segment. This
- * way, the sequence of multixact member segments will always have a
- * one-segment hole at a minimum. We start spewing warnings a few
- * complete segments before that.
+ * Determine the offset of the oldest multixact. Normally, we can read
+ * the offset from the multixact itself, but there's an important special
+ * case: if there are no multixacts in existence at all, oldestMXact
+ * obviously can't point to one. It will instead point to the multixact
+ * ID that will be assigned the next time one is needed.
+ *
+ * NB: oldestMXact should be the oldest multixact that still exists in
+ * the SLRU, unlike in SetMultiXactIdLimit, where we do this same
+ * computation based on the oldest value that might be referenced in a
+ * table.
*/
LWLockAcquire(MultiXactGenLock, LW_SHARED);
if (MultiXactState->nextMXact == oldestMXact)
nextOffset = MultiXactState->nextOffset;
oldestMultiXactId = MultiXactState->oldestMultiXactId;
nextMultiXactId = MultiXactState->nextMXact;
+ oldestOffset = MultiXactState->oldestOffset;
LWLockRelease(MultiXactGenLock);
- oldestOffset = find_multixact_start(oldestMultiXactId);
*members = nextOffset - oldestOffset;
*multixacts = nextMultiXactId - oldestMultiXactId;
}