diff options
author | Heikki Linnakangas <heikki.linnakangas@iki.fi> | 2019-07-24 20:24:07 +0300 |
---|---|---|
committer | Heikki Linnakangas <heikki.linnakangas@iki.fi> | 2019-07-24 20:24:07 +0300 |
commit | 6655a7299d835dea9e8e0ba69cc5284611b96f29 (patch) | |
tree | 3ef87e2ae30a08c0ea9cb3413443624352cdc661 /src/backend/access/gist | |
parent | 9eb5607e69933f0a88b6774d1ba728f27afdbd3d (diff) | |
download | postgresql-6655a7299d835dea9e8e0ba69cc5284611b96f29.tar.gz postgresql-6655a7299d835dea9e8e0ba69cc5284611b96f29.zip |
Use full 64-bit XID for checking if a deleted GiST page is old enough.
Otherwise, after a deleted page gets even older, it becomes unrecyclable
again. B-tree has the same problem, and has had since time immemorial,
but let's at least fix this in GiST, where this is new.
Backpatch to v12, where GiST page deletion was introduced.
Reviewed-by: Andrey Borodin
Discussion: https://www.postgresql.org/message-id/835A15A5-F1B4-4446-A711-BF48357EB602%40yandex-team.ru
Diffstat (limited to 'src/backend/access/gist')
-rw-r--r-- | src/backend/access/gist/gistutil.c | 24 | ||||
-rw-r--r-- | src/backend/access/gist/gistvacuum.c | 7 | ||||
-rw-r--r-- | src/backend/access/gist/gistxlog.c | 32 |
3 files changed, 49 insertions, 14 deletions
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 7d1b219bbc8..97260201dcd 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -882,9 +882,27 @@ gistNewBuffer(Relation r) bool gistPageRecyclable(Page page) { - return PageIsNew(page) || - (GistPageIsDeleted(page) && - TransactionIdPrecedes(GistPageGetDeleteXid(page), RecentGlobalXmin)); + if (PageIsNew(page)) + return true; + if (GistPageIsDeleted(page)) + { + /* + * The page was deleted, but when? If it was just deleted, a scan + * might have seen the downlink to it, and will read the page later. + * As long as that can happen, we must keep the deleted page around as + * a tombstone. + * + * Compare the deletion XID with RecentGlobalXmin. If deleteXid < + * RecentGlobalXmin, then no scan that's still in progress could have + * seen its downlink, and we can recycle it. + */ + FullTransactionId deletexid_full = GistPageGetDeleteXid(page); + FullTransactionId recentxmin_full = GetFullRecentGlobalXmin(); + + if (FullTransactionIdPrecedes(deletexid_full, recentxmin_full)) + return true; + } + return false; } bytea * diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c index 4270226eee2..bf754ea6d0d 100644 --- a/src/backend/access/gist/gistvacuum.c +++ b/src/backend/access/gist/gistvacuum.c @@ -595,7 +595,7 @@ gistdeletepage(IndexVacuumInfo *info, GistBulkDeleteResult *stats, ItemId iid; IndexTuple idxtuple; XLogRecPtr recptr; - TransactionId txid; + FullTransactionId txid; /* * Check that the leaf is still empty and deletable. @@ -648,14 +648,13 @@ gistdeletepage(IndexVacuumInfo *info, GistBulkDeleteResult *stats, * currently in progress must have ended. (That's much more conservative * than needed, but let's keep it safe and simple.) */ - txid = ReadNewTransactionId(); + txid = ReadNextFullTransactionId(); START_CRIT_SECTION(); /* mark the page as deleted */ MarkBufferDirty(leafBuffer); - GistPageSetDeleteXid(leafPage, txid); - GistPageSetDeleted(leafPage); + GistPageSetDeleted(leafPage, txid); stats->stats.pages_deleted++; /* remove the downlink from the parent */ diff --git a/src/backend/access/gist/gistxlog.c b/src/backend/access/gist/gistxlog.c index 503db34d863..3b28f546465 100644 --- a/src/backend/access/gist/gistxlog.c +++ b/src/backend/access/gist/gistxlog.c @@ -356,8 +356,7 @@ gistRedoPageDelete(XLogReaderState *record) { Page page = (Page) BufferGetPage(leafBuffer); - GistPageSetDeleteXid(page, xldata->deleteXid); - GistPageSetDeleted(page); + GistPageSetDeleted(page, xldata->deleteXid); PageSetLSN(page, lsn); MarkBufferDirty(leafBuffer); @@ -396,8 +395,27 @@ gistRedoPageReuse(XLogReaderState *record) */ if (InHotStandby) { - ResolveRecoveryConflictWithSnapshot(xlrec->latestRemovedXid, - xlrec->node); + FullTransactionId latestRemovedFullXid = xlrec->latestRemovedFullXid; + FullTransactionId nextFullXid = ReadNextFullTransactionId(); + uint64 diff; + + /* + * ResolveRecoveryConflictWithSnapshot operates on 32-bit + * TransactionIds, so truncate the logged FullTransactionId. If the + * logged value is very old, so that XID wrap-around already happened + * on it, there can't be any snapshots that still see it. + */ + nextFullXid = ReadNextFullTransactionId(); + diff = U64FromFullTransactionId(nextFullXid) - + U64FromFullTransactionId(latestRemovedFullXid); + if (diff < MaxTransactionId / 2) + { + TransactionId latestRemovedXid; + + latestRemovedXid = XidFromFullTransactionId(latestRemovedFullXid); + ResolveRecoveryConflictWithSnapshot(latestRemovedXid, + xlrec->node); + } } } @@ -554,7 +572,7 @@ gistXLogSplit(bool page_is_leaf, * downlink from the parent page. */ XLogRecPtr -gistXLogPageDelete(Buffer buffer, TransactionId xid, +gistXLogPageDelete(Buffer buffer, FullTransactionId xid, Buffer parentBuffer, OffsetNumber downlinkOffset) { gistxlogPageDelete xlrec; @@ -578,7 +596,7 @@ gistXLogPageDelete(Buffer buffer, TransactionId xid, * Write XLOG record about reuse of a deleted page. */ void -gistXLogPageReuse(Relation rel, BlockNumber blkno, TransactionId latestRemovedXid) +gistXLogPageReuse(Relation rel, BlockNumber blkno, FullTransactionId latestRemovedXid) { gistxlogPageReuse xlrec_reuse; @@ -591,7 +609,7 @@ gistXLogPageReuse(Relation rel, BlockNumber blkno, TransactionId latestRemovedXi /* XLOG stuff */ xlrec_reuse.node = rel->rd_node; xlrec_reuse.block = blkno; - xlrec_reuse.latestRemovedXid = latestRemovedXid; + xlrec_reuse.latestRemovedFullXid = latestRemovedXid; XLogBeginInsert(); XLogRegisterData((char *) &xlrec_reuse, SizeOfGistxlogPageReuse); |