visibility-map-v1.patch

application/octet-stream

Filename: visibility-map-v1.patch
Type: application/octet-stream
Part: 0
Message: crash-safe visibility map, take four

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: context
Series: patch v1
File+
src/backend/access/heap/heapam.c 115 0
src/backend/access/heap/visibilitymap.c 13 9
src/backend/access/transam/transam.c 2 0
src/backend/access/transam/xlog.c 0 0
src/backend/commands/vacuumlazy.c 2 0
src/include/access/heapam.h 2 0
src/include/access/htup.h 10 0
src/include/access/transam.h 3 0
*** a/src/backend/access/heap/heapam.c
--- b/src/backend/access/heap/heapam.c
***************
*** 4044,4049 **** log_heap_freeze(Relation reln, Buffer buffer,
--- 4044,4081 ----
  }
  
  /*
+  * Perform XLogInsert for a heap-visible operation.	 'block' is the block
+  * being marked all-visible, and vm_buffer is the buffer containing the
+  * corresponding visibility map block.  Both should have already been modified
+  * and dirtied.
+  */
+ XLogRecPtr
+ log_heap_visible(RelFileNode rnode, BlockNumber block, Buffer vm_buffer)
+ {
+ 	xl_heap_visible xlrec;
+ 	XLogRecPtr	recptr;
+ 	XLogRecData rdata[2];
+ 
+ 	xlrec.node = rnode;
+ 	xlrec.block = block;
+ 
+ 	rdata[0].data = (char *) &xlrec;
+ 	rdata[0].len = SizeOfHeapVisible;
+ 	rdata[0].buffer = InvalidBuffer;
+ 	rdata[0].next = &(rdata[1]);
+ 
+ 	rdata[1].data = NULL;
+ 	rdata[1].len = 0;
+ 	rdata[1].buffer = vm_buffer;
+ 	rdata[1].buffer_std = false;
+ 	rdata[1].next = NULL;
+ 
+ 	recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_VISIBLE, rdata);
+ 
+ 	return recptr;
+ }
+ 
+ /*
   * Perform XLogInsert for a heap-update operation.	Caller must already
   * have modified the buffer(s) and marked them dirty.
   */
***************
*** 4331,4336 **** heap_xlog_freeze(XLogRecPtr lsn, XLogRecord *record)
--- 4363,4440 ----
  	UnlockReleaseBuffer(buffer);
  }
  
+ /*
+  * Replay XLOG_HEAP2_VISIBLE record.
+  *
+  * The critical integrity requirement here is that we must never end up with
+  * a situation where the visibility map bit is set, and the page-level
+  * PD_ALL_VISIBLE bit is clear.  If that were to occur, then a subsequent
+  * page modification would fail to clear the visibility map bit.
+  */
+ static void
+ heap_xlog_visible(XLogRecPtr lsn, XLogRecord *record)
+ {
+ 	xl_heap_visible *xlrec = (xl_heap_visible *) XLogRecGetData(record);
+ 	Buffer		buffer;
+ 	Page		page;
+ 
+ 	/*
+ 	 * Read the heap page, if it still exists.  If the heap file has been
+ 	 * dropped or truncated later in recovery, this might fail.  In that case,
+ 	 * there's no point in doing anything further, since the visibility map
+ 	 * will have to be cleared out at the same time.
+ 	 */
+ 	buffer = XLogReadBufferExtended(xlrec->node, MAIN_FORKNUM, xlrec->block,
+ 									RBM_NORMAL);
+ 	if (!BufferIsValid(buffer))
+ 		return;
+ 	page = (Page) BufferGetPage(buffer);
+ 
+ 	/*
+ 	 * We don't bump the LSN of the heap page when setting the visibility
+ 	 * map bit, because that would generate an unworkable volume of
+ 	 * full-page writes.  This exposes us to torn page hazards, but since
+ 	 * we're not inspecting the existing page contents in any way, we
+ 	 * don't care.
+ 	 *
+ 	 * However, all operations that clear the visibility map bit *do* bump
+ 	 * the LSN, and those operations will only be replayed if the XLOG LSN
+ 	 * precedes the page LSN.  Thus, if the page LSN has advanced past our
+ 	 * XLOG record's LSN, we mustn't mark the page all-visible, because
+ 	 * the subsequent update won't be replayed to clear the flag.
+ 	 */
+ 	if (XLByteLE(lsn, PageGetLSN(page)))
+ 	{
+ 		PageSetAllVisible(page);
+ 		MarkBufferDirty(buffer);
+ 	}
+ 
+ 	/* Done with heap page. */
+ 	UnlockReleaseBuffer(buffer);
+ 
+ 	/*
+ 	 * Even we skipped the heap page update due to the LSN interlock, it's
+ 	 * still safe to update the visibility map.  Any WAL record that clears
+ 	 * the visibility map bit does so before checking the page LSN, so any
+ 	 * bits that need to be cleared will still be cleared.
+ 	 */
+ 	if (record->xl_info & XLR_BKP_BLOCK_1)
+ 		RestoreBkpBlocks(lsn, record, false);
+ 	else
+ 	{
+ 		Relation	reln;
+ 		Buffer		vmbuffer = InvalidBuffer;
+ 
+ 		reln = CreateFakeRelcacheEntry(xlrec->node);
+ 		visibilitymap_pin(reln, xlrec->block, &vmbuffer);
+ 		/* Don't set the bit if replay has already passed this point. */
+ 		if (XLByteLE(lsn, PageGetLSN(BufferGetPage(vmbuffer))))
+ 			visibilitymap_set(reln, xlrec->block, lsn, &vmbuffer);
+ 		ReleaseBuffer(vmbuffer);
+ 		FreeFakeRelcacheEntry(reln);
+ 	}
+ }
+ 
  static void
  heap_xlog_newpage(XLogRecPtr lsn, XLogRecord *record)
  {
***************
*** 4923,4928 **** heap2_redo(XLogRecPtr lsn, XLogRecord *record)
--- 5027,5035 ----
  		case XLOG_HEAP2_CLEANUP_INFO:
  			heap_xlog_cleanup_info(lsn, record);
  			break;
+ 		case XLOG_HEAP2_VISIBLE:
+ 			heap_xlog_visible(lsn, record);
+ 			break;
  		default:
  			elog(PANIC, "heap2_redo: unknown op code %u", info);
  	}
***************
*** 5052,5057 **** heap2_desc(StringInfo buf, uint8 xl_info, char *rec)
--- 5159,5172 ----
  		appendStringInfo(buf, "cleanup info: remxid %u",
  						 xlrec->latestRemovedXid);
  	}
+ 	else if (info == XLOG_HEAP2_VISIBLE)
+ 	{
+ 		xl_heap_visible *xlrec = (xl_heap_visible *) rec;
+ 
+ 		appendStringInfo(buf, "visible: rel %u/%u/%u; blk %u",
+ 						 xlrec->node.spcNode, xlrec->node.dbNode,
+ 						 xlrec->node.relNode, xlrec->block);
+ 	}
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
*** a/src/backend/access/heap/visibilitymap.c
--- b/src/backend/access/heap/visibilitymap.c
***************
*** 76,94 ****
   * index would still be visible to all other backends, and deletions wouldn't
   * be visible to other backends yet.  (But HOT breaks that argument, no?)
   *
-  * There's another hole in the way the PD_ALL_VISIBLE flag is set. When
-  * vacuum observes that all tuples are visible to all, it sets the flag on
-  * the heap page, and also sets the bit in the visibility map. If we then
-  * crash, and only the visibility map page was flushed to disk, we'll have
-  * a bit set in the visibility map, but the corresponding flag on the heap
-  * page is not set. If the heap page is then updated, the updater won't
-  * know to clear the bit in the visibility map.  (Isn't that prevented by
-  * the LSN interlock?)
-  *
   *-------------------------------------------------------------------------
   */
  #include "postgres.h"
  
  #include "access/visibilitymap.h"
  #include "storage/bufmgr.h"
  #include "storage/bufpage.h"
--- 76,86 ----
   * index would still be visible to all other backends, and deletions wouldn't
   * be visible to other backends yet.  (But HOT breaks that argument, no?)
   *
   *-------------------------------------------------------------------------
   */
  #include "postgres.h"
  
+ #include "access/heapam.h"
  #include "access/visibilitymap.h"
  #include "storage/bufmgr.h"
  #include "storage/bufpage.h"
***************
*** 196,208 **** visibilitymap_pin(Relation rel, BlockNumber heapBlk, Buffer *buf)
  /*
   *	visibilitymap_set - set a bit on a previously pinned page
   *
!  * recptr is the LSN of the heap page. The LSN of the visibility map page is
!  * advanced to that, to make sure that the visibility map doesn't get flushed
!  * to disk before the update to the heap page that made all tuples visible.
   *
!  * This is an opportunistic function. It does nothing, unless *buf
!  * contains the bit for heapBlk. Call visibilitymap_pin first to pin
!  * the right map page. This function doesn't do any I/O.
   */
  void
  visibilitymap_set(Relation rel, BlockNumber heapBlk, XLogRecPtr recptr,
--- 188,201 ----
  /*
   *	visibilitymap_set - set a bit on a previously pinned page
   *
!  * recptr is the LSN of the XLOG record we're replaying, if we're in recovery,
!  * or InvalidXLogRecPtr in normal running.  The page LSN is advanced to the
!  * one provided; in normal running, we generate a new XLOG record and set the
!  * page LSN to that value.
   *
!  * You must pass a buffer containing the correct map page to this function.
!  * Call visibilitymap_pin first to pin the right one. This function doesn't do
!  * any I/O.
   */
  void
  visibilitymap_set(Relation rel, BlockNumber heapBlk, XLogRecPtr recptr,
***************
*** 220,226 **** visibilitymap_set(Relation rel, BlockNumber heapBlk, XLogRecPtr recptr,
  
  	/* Check that we have the right page pinned */
  	if (!BufferIsValid(*buf) || BufferGetBlockNumber(*buf) != mapBlock)
! 		return;
  
  	page = BufferGetPage(*buf);
  	map = PageGetContents(page);
--- 213,219 ----
  
  	/* Check that we have the right page pinned */
  	if (!BufferIsValid(*buf) || BufferGetBlockNumber(*buf) != mapBlock)
! 		elog(ERROR, "wrong buffer passed to visibilitymap_set");
  
  	page = BufferGetPage(*buf);
  	map = PageGetContents(page);
***************
*** 230,236 **** visibilitymap_set(Relation rel, BlockNumber heapBlk, XLogRecPtr recptr,
  	{
  		map[mapByte] |= (1 << mapBit);
  
! 		if (XLByteLT(PageGetLSN(page), recptr))
  			PageSetLSN(page, recptr);
  		PageSetTLI(page, ThisTimeLineID);
  		MarkBufferDirty(*buf);
--- 223,232 ----
  	{
  		map[mapByte] |= (1 << mapBit);
  
! 		if (XLogRecPtrIsInvalid(recptr) && RelationNeedsWAL(rel))
! 			recptr = log_heap_visible(rel->rd_node, heapBlk, *buf);
! 
! 		if (!XLogRecPtrIsInvalid(recptr))
  			PageSetLSN(page, recptr);
  		PageSetTLI(page, ThisTimeLineID);
  		MarkBufferDirty(*buf);
*** a/src/backend/access/transam/transam.c
--- b/src/backend/access/transam/transam.c
***************
*** 24,29 ****
--- 24,31 ----
  #include "access/transam.h"
  #include "utils/snapmgr.h"
  
+ /* Handy constant for an invalid xlog recptr */
+ const XLogRecPtr InvalidXLogRecPtr = {0, 0};
  
  /*
   * Single-item cache for results of TransactionLogFetch.  It's worth having
***************
*** 35,43 **** static TransactionId cachedFetchXid = InvalidTransactionId;
  static XidStatus cachedFetchXidStatus;
  static XLogRecPtr cachedCommitLSN;
  
- /* Handy constant for an invalid xlog recptr */
- static const XLogRecPtr InvalidXLogRecPtr = {0, 0};
- 
  /* Local functions */
  static XidStatus TransactionLogFetch(TransactionId transactionId);
  
--- 37,42 ----
*** a/src/backend/access/transam/xlog.c
--- b/src/backend/access/transam/xlog.c
***************
*** 5410,5416 **** exitArchiveRecovery(TimeLineID endTLI, uint32 endLogId, uint32 endLogSeg)
  {
  	char		recoveryPath[MAXPGPATH];
  	char		xlogpath[MAXPGPATH];
- 	XLogRecPtr	InvalidXLogRecPtr = {0, 0};
  
  	/*
  	 * We are no longer in archive recovery state.
--- 5410,5415 ----
***************
*** 7997,8004 **** CreateRestartPoint(int flags)
  	if (XLogRecPtrIsInvalid(lastCheckPointRecPtr) ||
  		XLByteLE(lastCheckPoint.redo, ControlFile->checkPointCopy.redo))
  	{
- 		XLogRecPtr	InvalidXLogRecPtr = {0, 0};
- 
  		ereport(DEBUG2,
  				(errmsg("skipping restartpoint, already performed at %X/%X",
  				  lastCheckPoint.redo.xlogid, lastCheckPoint.redo.xrecoff)));
--- 7996,8001 ----
*** a/src/backend/commands/vacuumlazy.c
--- b/src/backend/commands/vacuumlazy.c
***************
*** 479,485 **** lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
  				visibilitymap_pin(onerel, blkno, &vmbuffer);
  				LockBuffer(buf, BUFFER_LOCK_SHARE);
  				if (PageIsAllVisible(page))
! 					visibilitymap_set(onerel, blkno, PageGetLSN(page), &vmbuffer);
  				LockBuffer(buf, BUFFER_LOCK_UNLOCK);
  			}
  
--- 479,486 ----
  				visibilitymap_pin(onerel, blkno, &vmbuffer);
  				LockBuffer(buf, BUFFER_LOCK_SHARE);
  				if (PageIsAllVisible(page))
! 					visibilitymap_set(onerel, blkno, InvalidXLogRecPtr,
! 									  &vmbuffer);
  				LockBuffer(buf, BUFFER_LOCK_UNLOCK);
  			}
  
*** a/src/include/access/heapam.h
--- b/src/include/access/heapam.h
***************
*** 136,141 **** extern XLogRecPtr log_heap_clean(Relation reln, Buffer buffer,
--- 136,143 ----
  extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
  				TransactionId cutoff_xid,
  				OffsetNumber *offsets, int offcnt);
+ extern XLogRecPtr log_heap_visible(RelFileNode rnode, BlockNumber block,
+ 				 Buffer vm_buffer);
  extern XLogRecPtr log_newpage(RelFileNode *rnode, ForkNumber forkNum,
  			BlockNumber blk, Page page);
  
*** a/src/include/access/htup.h
--- b/src/include/access/htup.h
***************
*** 606,611 **** typedef HeapTupleData *HeapTuple;
--- 606,612 ----
  #define XLOG_HEAP2_CLEAN		0x10
  /* 0x20 is free, was XLOG_HEAP2_CLEAN_MOVE */
  #define XLOG_HEAP2_CLEANUP_INFO 0x30
+ #define XLOG_HEAP2_VISIBLE		0x40
  
  /*
   * All what we need to find changed tuple
***************
*** 750,755 **** typedef struct xl_heap_freeze
--- 751,765 ----
  
  #define SizeOfHeapFreeze (offsetof(xl_heap_freeze, cutoff_xid) + sizeof(TransactionId))
  
+ /* This is what we need to know about setting a visibility map bit */
+ typedef struct xl_heap_visible
+ {
+ 	RelFileNode node;
+ 	BlockNumber block;
+ } xl_heap_visible;
+ 
+ #define SizeOfHeapVisible (offsetof(xl_heap_visible, block) + sizeof(BlockNumber))
+ 
  extern void HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
  									   TransactionId *latestRemovedXid);
  
*** a/src/include/access/transam.h
--- b/src/include/access/transam.h
***************
*** 135,140 **** extern bool TransactionStartedDuringRecovery(void);
--- 135,143 ----
  /* in transam/varsup.c */
  extern PGDLLIMPORT VariableCache ShmemVariableCache;
  
+ /* in transam/transam.c */
+ extern const XLogRecPtr InvalidXLogRecPtr;
+ 
  
  /*
   * prototypes for functions in transam/transam.c