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