From 4cd3fb6e1244383fc9f77906e7162de0559ba354 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Tue, 8 Mar 2011 12:07:29 +0200 Subject: [PATCH] Truncate predicate lock manager's SLRU lazily at checkpoint. That's safer than doing it aggressively whenever the tail-XID pointer is advanced, because this way we don't need to do it while holding SerializableXactHashLock. This also fixes bug #5915 spotted by YAMAMOTO Takashi, and removes an obsolete comment spotted by Kevin Grittner. --- src/backend/access/transam/xlog.c | 2 + src/backend/storage/lmgr/predicate.c | 117 +++++++++++++++------------ src/include/storage/predicate.h | 2 + 3 files changed, 69 insertions(+), 52 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f86d9ebdda..ddcad46b7a 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -48,6 +48,7 @@ #include "storage/ipc.h" #include "storage/latch.h" #include "storage/pmsignal.h" +#include "storage/predicate.h" #include "storage/procarray.h" #include "storage/reinit.h" #include "storage/smgr.h" @@ -7875,6 +7876,7 @@ CheckPointGuts(XLogRecPtr checkPointRedo, int flags) CheckPointCLOG(); CheckPointSUBTRANS(); CheckPointMultiXact(); + CheckPointPredicate(); CheckPointRelationMap(); CheckPointBuffers(flags); /* performs all required fsyncs */ /* We deliberately delay 2PC checkpointing as long as possible */ diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c index 700c0db927..15f0a64d3c 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -323,10 +323,9 @@ static SlruCtlData OldSerXidSlruCtlData; typedef struct OldSerXidControlData { - int headPage; - int tailSegment; - TransactionId headXid; - TransactionId tailXid; + int headPage; /* newest initialized page */ + TransactionId headXid; /* newest valid Xid in the SLRU */ + TransactionId tailXid; /* oldest xmin we might be interested in */ bool warningIssued; } OldSerXidControlData; @@ -711,7 +710,6 @@ OldSerXidInit(void) * Set control information to reflect empty SLRU. */ oldSerXidControl->headPage = -1; - oldSerXidControl->tailSegment = -1; oldSerXidControl->headXid = InvalidTransactionId; oldSerXidControl->tailXid = InvalidTransactionId; oldSerXidControl->warningIssued = false; @@ -722,10 +720,6 @@ OldSerXidInit(void) * Record a committed read write serializable xid and the minimum * commitSeqNo of any transactions to which this xid had a rw-conflict out. * A zero seqNo means that there were no conflicts out from xid. - * - * The return value is normally false -- true means that we're about to - * wrap around our space for tracking these xids, so the caller might want - * to take action to prevent that. */ static void OldSerXidAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo) @@ -733,7 +727,7 @@ OldSerXidAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo) TransactionId tailXid; int targetPage; int slotno; - int page; + int firstZeroPage; int xidSpread; bool isNewPage; @@ -745,30 +739,34 @@ OldSerXidAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo) /* * If no serializable transactions are active, there shouldn't be anything - * to push out to this SLRU. Hitting this assert would mean there's + * to push out to the SLRU. Hitting this assert would mean there's * something wrong with the earlier cleanup logic. */ tailXid = oldSerXidControl->tailXid; Assert(TransactionIdIsValid(tailXid)); + /* + * If the SLRU is currently unused, zero out the whole active region + * from tailXid to headXid before taking it into use. Otherwise zero + * out only any new pages that enter the tailXid-headXid range as we + * advance headXid. + */ if (oldSerXidControl->headPage < 0) { - page = OldSerXidPage(tailXid); - oldSerXidControl->tailSegment = OldSerXidSegment(page); - page = oldSerXidControl->tailSegment * OLDSERXID_ENTRIESPERPAGE; + firstZeroPage = OldSerXidPage(tailXid); isNewPage = true; } else { - page = OldSerXidNextPage(oldSerXidControl->headPage); - isNewPage = OldSerXidPagePrecedesLogically(oldSerXidControl->headPage, targetPage); + firstZeroPage = OldSerXidNextPage(oldSerXidControl->headPage); + isNewPage = OldSerXidPagePrecedesLogically(oldSerXidControl->headPage, + targetPage); } if (!TransactionIdIsValid(oldSerXidControl->headXid) || TransactionIdFollows(xid, oldSerXidControl->headXid)) oldSerXidControl->headXid = xid; - if (oldSerXidControl->headPage < 0 - || OldSerXidPagePrecedesLogically(oldSerXidControl->headPage, targetPage)) + if (isNewPage) oldSerXidControl->headPage = targetPage; xidSpread = (((uint32) xid) - ((uint32) tailXid)); @@ -788,10 +786,10 @@ OldSerXidAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo) if (isNewPage) { /* Initialize intervening pages. */ - while (page != targetPage) + while (firstZeroPage != targetPage) { - (void) SimpleLruZeroPage(OldSerXidSlruCtl, page); - page = OldSerXidNextPage(page); + (void) SimpleLruZeroPage(OldSerXidSlruCtl, firstZeroPage); + firstZeroPage = OldSerXidNextPage(firstZeroPage); } slotno = SimpleLruZeroPage(OldSerXidSlruCtl, targetPage); } @@ -846,31 +844,24 @@ OldSerXidGetMinConflictCommitSeqNo(TransactionId xid) /* * Call this whenever there is a new xmin for active serializable * transactions. We don't need to keep information on transactions which - * preceed that. InvalidTransactionId means none active, so everything in - * the SLRU should be discarded. + * precede that. InvalidTransactionId means none active, so everything in + * the SLRU can be discarded. */ static void OldSerXidSetActiveSerXmin(TransactionId xid) { - int newTailPage; - int newTailSegment; - LWLockAcquire(OldSerXidLock, LW_EXCLUSIVE); /* * When no sxacts are active, nothing overlaps, set the xid values to - * invalid to show that there are no valid entries. Don't clear the - * segment/page information, though. A new xmin might still land in an - * existing segment, and we don't want to repeatedly delete and re-create - * the same segment file. + * invalid to show that there are no valid entries. Don't clear headPage, + * though. A new xmin might still land on that page, and we don't want + * to repeatedly zero out the same page. */ if (!TransactionIdIsValid(xid)) { - if (TransactionIdIsValid(oldSerXidControl->tailXid)) - { - oldSerXidControl->headXid = InvalidTransactionId; - oldSerXidControl->tailXid = InvalidTransactionId; - } + oldSerXidControl->tailXid = InvalidTransactionId; + oldSerXidControl->headXid = InvalidTransactionId; LWLockRelease(OldSerXidLock); return; } @@ -886,7 +877,9 @@ OldSerXidSetActiveSerXmin(TransactionId xid) Assert(oldSerXidControl->headPage < 0); if (!TransactionIdIsValid(oldSerXidControl->tailXid) || TransactionIdPrecedes(xid, oldSerXidControl->tailXid)) + { oldSerXidControl->tailXid = xid; + } LWLockRelease(OldSerXidLock); return; } @@ -896,37 +889,57 @@ OldSerXidSetActiveSerXmin(TransactionId xid) oldSerXidControl->tailXid = xid; - /* Exit quickly if there are no segments active. */ + LWLockRelease(OldSerXidLock); +} + +/* + * Perform a checkpoint --- either during shutdown, or on-the-fly + * + * We don't have any data that needs to survive a restart, but this is a + * convenient place to truncate the SLRU. + */ +void +CheckPointPredicate(void) +{ + int tailPage; + + LWLockAcquire(OldSerXidLock, LW_EXCLUSIVE); + + /* Exit quickly if the SLRU is currently not in use. */ if (oldSerXidControl->headPage < 0) { LWLockRelease(OldSerXidLock); return; } - newTailPage = OldSerXidPage(xid); - newTailSegment = OldSerXidSegment(newTailPage); - - /* Exit quickly if we're still on the same segment. */ - if (newTailSegment == oldSerXidControl->tailSegment) + if (TransactionIdIsValid(oldSerXidControl->tailXid)) { - LWLockRelease(OldSerXidLock); - return; + /* We can truncate the SLRU up to the page containing tailXid */ + tailPage = OldSerXidPage(oldSerXidControl->tailXid); } - - oldSerXidControl->tailSegment = newTailSegment; - - /* See if that has cleared the last segment. */ - if (OldSerXidPagePrecedesLogically(oldSerXidControl->headPage, - newTailSegment * SLRU_PAGES_PER_SEGMENT)) + else { - oldSerXidControl->headXid = InvalidTransactionId; + /* + * The SLRU is no longer needed. Truncate everything but the last + * page. We don't dare to touch the last page in case the SLRU is + * taken back to use, and the new tail falls on the same page. + */ + tailPage = oldSerXidControl->headPage; oldSerXidControl->headPage = -1; - oldSerXidControl->tailSegment = -1; } LWLockRelease(OldSerXidLock); - SimpleLruTruncate(OldSerXidSlruCtl, newTailPage); + /* + * Flush dirty SLRU pages to disk + * + * This is not actually necessary from a correctness point of view. We do + * it merely as a debugging aid. + */ + SimpleLruFlush(OldSerXidSlruCtl, true); + + /* Truncate away pages that are no longer required */ + SimpleLruTruncate(OldSerXidSlruCtl, tailPage); } /*------------------------------------------------------------------------*/ diff --git a/src/include/storage/predicate.h b/src/include/storage/predicate.h index 163d8cb3ff..9a26ecf2d3 100644 --- a/src/include/storage/predicate.h +++ b/src/include/storage/predicate.h @@ -36,6 +36,8 @@ extern int max_predicate_locks_per_xact; extern void InitPredicateLocks(void); extern Size PredicateLockShmemSize(void); +extern void CheckPointPredicate(void); + /* predicate lock reporting */ extern bool PageIsPredicateLocked(const Relation relation, const BlockNumber blkno);