From 04e6d3b877e060d8445eb653b7ea26b1ee5cec6b Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Mon, 11 May 2015 10:51:14 -0400 Subject: [PATCH] Even when autovacuum=off, force it for members as we do in other cases. Thomas Munro, with some adjustments by me. --- src/backend/access/transam/multixact.c | 61 ++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index be94a87c5c..fc0e4e7400 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -204,6 +204,7 @@ typedef struct MultiXactStateData */ MultiXactId oldestMultiXactId; Oid oldestMultiXactDB; + MultiXactOffset oldestOffset; /* * This is what the previous checkpoint stored as the truncate position. @@ -954,14 +955,17 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset) * 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 @@ -2142,6 +2146,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid) MultiXactId multiStopLimit; MultiXactId multiWrapLimit; MultiXactId curMulti; + MultiXactOffset oldestOffset; + MultiXactOffset nextOffset; Assert(MultiXactIdIsValid(oldest_datminmxid)); @@ -2194,6 +2200,35 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid) 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; @@ -2202,7 +2237,9 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid) MultiXactState->multiWarnLimit = multiWarnLimit; MultiXactState->multiStopLimit = multiStopLimit; MultiXactState->multiWrapLimit = multiWrapLimit; + MultiXactState->oldestOffset = oldestOffset; curMulti = MultiXactState->nextMXact; + nextOffset = MultiXactState->nextOffset; LWLockRelease(MultiXactGenLock); /* Log the info */ @@ -2217,7 +2254,8 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid) * 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); @@ -2476,11 +2514,16 @@ DetermineSafeOldestOffset(MultiXactId oldestMXact) 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) @@ -2594,9 +2637,9 @@ ReadMultiXactCounts(uint32 *multixacts, MultiXactOffset *members) 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; }