From e1c26ab853dc4b816d2eb4c99b948fb099c0e243 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Thu, 9 Jun 2011 20:30:59 +0300 Subject: [PATCH] Fix the truncation logic of the OldSerXid SLRU mechanism. We can't pass SimpleLruTruncate() a page number that's "in the future", because it will issue a warning and refuse to truncate anything. Instead, we leave behind the latest segment. If the slru is not needed before XID wrap-around, the segment will appear as new again, and not be cleaned up until it gets old enough again. That's a bit unpleasant, but better than not cleaning up anything. Also, fix broken calculation to check and warn if the span of the OldSerXid SLRU is getting too large to fit in the 64k SLRU pages that we have available. It was not XID wraparound aware. Kevin Grittner and me. --- src/backend/storage/lmgr/predicate.c | 62 +++++++++++++++++++--------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c index 7fa649997d..8b1a3336ca 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -330,7 +330,7 @@ typedef struct OldSerXidControlData int headPage; /* newest initialized page */ TransactionId headXid; /* newest valid Xid in the SLRU */ TransactionId tailXid; /* oldest xmin we might be interested in */ - bool warningIssued; + bool warningIssued; /* have we issued SLRU wrap-around warning? */ } OldSerXidControlData; typedef struct OldSerXidControlData *OldSerXidControl; @@ -738,7 +738,6 @@ OldSerXidAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo) int targetPage; int slotno; int firstZeroPage; - int xidSpread; bool isNewPage; Assert(TransactionIdIsValid(xid)); @@ -779,18 +778,45 @@ OldSerXidAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo) if (isNewPage) oldSerXidControl->headPage = targetPage; - xidSpread = (((uint32) xid) - ((uint32) tailXid)); + /* + * Give a warning if we're about to run out of SLRU pages. + * + * slru.c has a maximum of 64k segments, with 32 (SLRU_PAGES_PER_SEGMENT) + * pages each. We need to store a 64-bit integer for each Xid, and with + * default 8k block size, 65536*32 pages is only enough to cover 2^30 + * XIDs. If we're about to hit that limit and wrap around, warn the user. + * + * To avoid spamming the user, we only give one warning when we've used 1 + * billion XIDs, and stay silent until the situation is fixed and the + * number of XIDs used falls below 800 million again. + * + * XXX: We have no safeguard to actually *prevent* the wrap-around, + * though. All you get is a warning. + */ if (oldSerXidControl->warningIssued) { - if (xidSpread < 800000000) + TransactionId lowWatermark; + + lowWatermark = tailXid + 800000000; + if (lowWatermark < FirstNormalTransactionId) + lowWatermark = FirstNormalTransactionId; + if (TransactionIdPrecedes(xid, lowWatermark)) oldSerXidControl->warningIssued = false; } - else if (xidSpread >= 1000000000) + else { - oldSerXidControl->warningIssued = true; - ereport(WARNING, - (errmsg("memory for serializable conflict tracking is nearly exhausted"), - errhint("There may be an idle transaction or a forgotten prepared transaction causing this."))); + TransactionId highWatermark; + + highWatermark = tailXid + 1000000000; + if (highWatermark < FirstNormalTransactionId) + highWatermark = FirstNormalTransactionId; + if (TransactionIdFollows(xid, highWatermark)) + { + oldSerXidControl->warningIssued = true; + ereport(WARNING, + (errmsg("memory for serializable conflict tracking is nearly exhausted"), + errhint("There may be an idle transaction or a forgotten prepared transaction causing this."))); + } } if (isNewPage) @@ -931,18 +957,16 @@ CheckPointPredicate(void) else { /* - * The SLRU is no longer needed. Truncate everything. If we try to - * leave the head page around to avoid re-zeroing it, we might not use - * the SLRU again until we're past the wrap-around point, which makes - * SLRU unhappy. + * The SLRU is no longer needed. Truncate to head before we set head + * invalid. * - * While the API asks you to specify truncation by page, it silently - * ignores the request unless the specified page is in a segment past - * some allocated portion of the SLRU. We don't care which page in a - * later segment we hit, so just add the number of pages per segment - * to the head page to land us *somewhere* in the next segment. + * XXX: It's possible that the SLRU is not needed again until XID + * wrap-around has happened, so that the segment containing headPage + * that we leave behind will appear to be new again. In that case it + * won't be removed until XID horizon advances enough to make it + * current again. */ - tailPage = oldSerXidControl->headPage + SLRU_PAGES_PER_SEGMENT; + tailPage = oldSerXidControl->headPage; oldSerXidControl->headPage = -1; }