btree-page-del-v1.patch

text/plain

Filename: btree-page-del-v1.patch
Type: text/plain
Part: 0
Message: B-tree page deletion boundary cases

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
Series: patch v1
File+
src/backend/access/nbtree/README 0 0
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);