diff --git a/src/backend/access/nbtree/README b/src/backend/access/nbtree/README index 561ffbb..6932c2b 100644 *** a/src/backend/access/nbtree/README --- b/src/backend/access/nbtree/README *************** *** 261,272 **** we need to be sure we don't miss or re-scan any items. A deleted page can only be reclaimed once there is no scan or search that has a reference to it; until then, it must stay in place with its ! right-link undisturbed. We implement this by waiting until all ! transactions that were running at the time of deletion are dead; which is overly strong, but is simple to implement within Postgres. When marked dead, a deleted page is labeled with the next-transaction counter value. VACUUM can reclaim the page for re-use when this transaction number is ! older than the oldest open transaction. Reclaiming a page doesn't actually change its state on disk --- we simply record it in the shared-memory free space map, from which it will be --- 261,274 ---- A deleted page can only be reclaimed once there is no scan or search that has a reference to it; until then, it must stay in place with its ! right-link undisturbed. We implement this by waiting until all active ! snapshots and registered snapshots as of the deletion are gone; which is overly strong, but is simple to implement within Postgres. When marked dead, a deleted page is labeled with the next-transaction counter value. VACUUM can reclaim the page for re-use when this transaction number is ! older than RecentGlobalXmin. As collateral damage, this implementation ! also waits for running XIDs with no snapshots and for snapshots taken ! until the next transaction to allocate an XID commits. Reclaiming a page doesn't actually change its state on disk --- we simply record it in the shared-memory free space map, from which it will be diff --git a/src/backend/access/nbtree/index c5e147f..e6dec61 100644 *** a/src/backend/access/nbtree/nbtpage.c --- b/src/backend/access/nbtree/nbtpage.c *************** *** 558,576 **** _bt_getbuf(Relation rel, BlockNumber blkno, int access) */ if (XLogStandbyInfoActive()) { - TransactionId latestRemovedXid; - BTPageOpaque opaque = (BTPageOpaque) PageGetSpecialPointer(page); ! /* ! * opaque->btpo.xact is the threshold value not the ! * value to measure conflicts against. We must retreat ! * by one from it to get the correct conflict xid. ! */ ! latestRemovedXid = opaque->btpo.xact; ! TransactionIdRetreat(latestRemovedXid); ! ! _bt_log_reuse_page(rel, blkno, latestRemovedXid); } /* Okay to use page. Re-initialize and return it */ --- 558,566 ---- */ if (XLogStandbyInfoActive()) { BTPageOpaque opaque = (BTPageOpaque) PageGetSpecialPointer(page); ! _bt_log_reuse_page(rel, blkno, opaque->btpo.xact); } /* Okay to use page. Re-initialize and return it */ *************** *** 685,691 **** bool _bt_page_recyclable(Page page) { BTPageOpaque opaque; - TransactionId cutoff; /* * It's possible to find an all-zeroes page in an index --- for example, a --- 675,680 ---- *************** *** 698,715 **** _bt_page_recyclable(Page page) /* * Otherwise, recycle if deleted and too old to have any processes ! * interested in it. If we are generating records for Hot Standby ! * defer page recycling until RecentGlobalXmin to respect user ! * controls specified by vacuum_defer_cleanup_age or hot_standby_feedback. */ - if (XLogStandbyInfoActive()) - cutoff = RecentGlobalXmin; - else - cutoff = RecentXmin; - opaque = (BTPageOpaque) PageGetSpecialPointer(page); if (P_ISDELETED(opaque) && ! TransactionIdPrecedesOrEquals(opaque->btpo.xact, cutoff)) return true; return false; } --- 687,697 ---- /* * Otherwise, recycle if deleted and too old to have any processes ! * interested in it. */ opaque = (BTPageOpaque) PageGetSpecialPointer(page); if (P_ISDELETED(opaque) && ! TransactionIdPrecedes(opaque->btpo.xact, RecentGlobalXmin)) return true; return false; } *************** *** 1376,1382 **** _bt_pagedel(Relation rel, Buffer buf, BTStack stack) /* * Mark the page itself deleted. It can be recycled when all current ! * transactions are gone. */ page = BufferGetPage(buf); opaque = (BTPageOpaque) PageGetSpecialPointer(page); --- 1358,1370 ---- /* * Mark the page itself deleted. It can be recycled when all current ! * transactions are gone. Storing GetTopTransactionId() would work, but ! * we're in VACUUM and would not otherwise have an XID. Having already ! * updated links to the target, ReadNewTransactionId() suffices as an ! * upper bound. Any scan having retained a now-stale link is advertising ! * in its PGXACT an xmin less than or equal to the value we read here. It ! * will continue to do so, holding back RecentGlobalXmin, for the duration ! * of that scan. */ page = BufferGetPage(buf); opaque = (BTPageOpaque) PageGetSpecialPointer(page); diff --git a/src/backend/access/nbtree/nbtindex 3b351a8..deca38c 100644 *** a/src/backend/access/nbtree/nbtxlog.c --- b/src/backend/access/nbtree/nbtxlog.c *************** *** 968,974 **** btree_redo(XLogRecPtr lsn, XLogRecord *record) /* * Btree reuse page records exist to provide a conflict point * when we reuse pages in the index via the FSM. That's all it ! * does though. */ { xl_btree_reuse_page *xlrec = (xl_btree_reuse_page *) XLogRecGetData(record); --- 968,978 ---- /* * Btree reuse page records exist to provide a conflict point * when we reuse pages in the index via the FSM. That's all it ! * does though. latestRemovedXid was the page's btpo.xact. The ! * btpo.xact < RecentGlobalXmin test in _bt_page_recyclable() ! * conceptually mirrors the pgxact->xmin > limitXmin test in ! * GetConflictingVirtualXIDs(). Consequently, one XID value ! * achieves the same exclusion effect on master and standby. */ { xl_btree_reuse_page *xlrec = (xl_btree_reuse_page *) XLogRecGetData(record);