diff --git a/contrib/pg_surgery/heap_surgery.c b/contrib/pg_surgery/heap_surgery.c index 5945727c834..181b7d1e210 100644 --- a/contrib/pg_surgery/heap_surgery.c +++ b/contrib/pg_surgery/heap_surgery.c @@ -17,6 +17,7 @@ #include "access/visibilitymap.h" #include "access/xloginsert.h" #include "catalog/pg_am_d.h" +#include "catalog/pg_control.h" #include "miscadmin.h" #include "storage/bufmgr.h" #include "utils/acl.h" @@ -146,6 +147,7 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt) { Buffer buf; Buffer vmbuf = InvalidBuffer; + bool unlock_vmbuf = false; Page page; BlockNumber blkno; OffsetNumber curoff; @@ -233,11 +235,15 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt) } /* - * Before entering the critical section, pin the visibility map page - * if it appears to be necessary. + * Before entering the critical section, pin and lock the visibility + * map page if it appears to be necessary. */ if (heap_force_opt == HEAP_FORCE_KILL && PageIsAllVisible(page)) + { visibilitymap_pin(rel, blkno, &vmbuf); + LockBuffer(vmbuf, BUFFER_LOCK_EXCLUSIVE); + unlock_vmbuf = true; + } /* No ereport(ERROR) from here until all the changes are logged. */ START_CRIT_SECTION(); @@ -266,10 +272,11 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt) */ if (PageIsAllVisible(page)) { + if (visibilitymap_clear(rel->rd_locator, blkno, vmbuf, + VISIBILITYMAP_VALID_BITS)) + did_modify_vm = true; + PageClearAllVisible(page); - visibilitymap_clear(rel->rd_locator, blkno, vmbuf, - VISIBILITYMAP_VALID_BITS); - did_modify_vm = true; } } else @@ -320,18 +327,29 @@ heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt) /* XLOG stuff */ if (RelationNeedsWAL(rel)) - log_newpage_buffer(buf, true); - } + { + XLogRecPtr recptr; - /* WAL log the VM page if it was modified. */ - if (did_modify_vm && RelationNeedsWAL(rel)) - log_newpage_buffer(vmbuf, false); + XLogBeginInsert(); + XLogRegisterBuffer(0, buf, REGBUF_STANDARD | REGBUF_FORCE_IMAGE); + /* Include the VM page if it was modified */ + if (did_modify_vm) + XLogRegisterBuffer(1, vmbuf, REGBUF_FORCE_IMAGE); + recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI); + if (did_modify_vm) + PageSetLSN(BufferGetPage(vmbuf), recptr); + PageSetLSN(BufferGetPage(buf), recptr); + } + } END_CRIT_SECTION(); UnlockReleaseBuffer(buf); - if (vmbuf != InvalidBuffer) + if (unlock_vmbuf) + LockBuffer(vmbuf, BUFFER_LOCK_UNLOCK); + + if (BufferIsValid(vmbuf)) ReleaseBuffer(vmbuf); /* Update the current_start_ptr before moving to the next page. */ diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index e1396ecbea3..8b488cfd8f6 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -60,7 +60,8 @@ static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid, CommandId cid, uint32 options); static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, - Buffer newbuf, HeapTuple oldtup, + Buffer vmbuffer_old, Buffer newbuf, + Buffer vmbuffer_new, HeapTuple oldtup, HeapTuple newtup, HeapTuple old_key_tuple, bool all_visible_cleared, bool new_all_visible_cleared, bool walLogical); @@ -2009,7 +2010,8 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, Buffer buffer; Page page; Buffer vmbuffer = InvalidBuffer; - bool all_visible_cleared = false; + bool clear_all_visible = false; + bool vmbuffer_modified = false; /* Cheap, simplistic check that the tuple matches the rel's rowtype. */ Assert(HeapTupleHeaderGetNatts(tup->t_data) <= @@ -2053,19 +2055,28 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, */ CheckForSerializableConflictIn(relation, NULL, InvalidBlockNumber); + /* Lock the vmbuffer before the critical section */ + if (PageIsAllVisible(page)) + { + LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE); + clear_all_visible = true; + } + /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); RelationPutHeapTuple(relation, buffer, heaptup, (options & HEAP_INSERT_SPECULATIVE) != 0); - if (PageIsAllVisible(page)) + if (clear_all_visible) { - all_visible_cleared = true; + /* It's possible the VM bits were already clear */ + if (visibilitymap_clear(relation->rd_locator, + ItemPointerGetBlockNumber(&(heaptup->t_self)), + vmbuffer, VISIBILITYMAP_VALID_BITS)) + vmbuffer_modified = true; + PageClearAllVisible(page); - visibilitymap_clear(relation->rd_locator, - ItemPointerGetBlockNumber(&(heaptup->t_self)), - vmbuffer, VISIBILITYMAP_VALID_BITS); } /* @@ -2116,7 +2127,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, xlrec.offnum = ItemPointerGetOffsetNumber(&heaptup->t_self); xlrec.flags = 0; - if (all_visible_cleared) + if (clear_all_visible) xlrec.flags |= XLH_INSERT_ALL_VISIBLE_CLEARED; if (options & HEAP_INSERT_SPECULATIVE) xlrec.flags |= XLH_INSERT_IS_SPECULATIVE; @@ -2161,15 +2172,28 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, /* filtering by origin on a row level is much more efficient */ XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN); + if (vmbuffer_modified) + XLogRegisterBuffer(HEAP_INSERT_BLKREF_VM, vmbuffer, 0); + recptr = XLogInsert(RM_HEAP_ID, info); PageSetLSN(page, recptr); + + if (vmbuffer_modified) + PageSetLSN(BufferGetPage(vmbuffer), recptr); } END_CRIT_SECTION(); UnlockReleaseBuffer(buffer); - if (vmbuffer != InvalidBuffer) + + /* + * We locked vmbuffer if clear_all_visible was true regardless of whether + * or not we ended up modifying the vmbuffer. + */ + if (clear_all_visible) + LockBuffer(vmbuffer, BUFFER_LOCK_UNLOCK); + if (BufferIsValid(vmbuffer)) ReleaseBuffer(vmbuffer); /* @@ -2350,8 +2374,9 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, while (ndone < ntuples) { Buffer buffer; - bool all_visible_cleared = false; + bool clear_all_visible = false; bool all_frozen_set = false; + bool vmbuffer_modified = false; int nthispage; CHECK_FOR_INTERRUPTS(); @@ -2396,6 +2421,12 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, /* Lock the vmbuffer before entering the critical section */ LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE); } + else if (PageIsAllVisible(page) && !(options & HEAP_INSERT_FROZEN)) + { + clear_all_visible = true; + /* Lock the vmbuffer before entering the critical section */ + LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE); + } /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); @@ -2438,13 +2469,16 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, * page, mark it as all-frozen and update the visibility map. We're * already holding a pin on the vmbuffer. */ - if (PageIsAllVisible(page) && !(options & HEAP_INSERT_FROZEN)) + if (clear_all_visible) { - all_visible_cleared = true; + Assert(!(options & HEAP_INSERT_FROZEN)); + /* It's possible the VM bits were already clear */ + if (visibilitymap_clear(relation->rd_locator, + BufferGetBlockNumber(buffer), + vmbuffer, VISIBILITYMAP_VALID_BITS)) + vmbuffer_modified = true; + PageClearAllVisible(page); - visibilitymap_clear(relation->rd_locator, - BufferGetBlockNumber(buffer), - vmbuffer, VISIBILITYMAP_VALID_BITS); } else if (all_frozen_set) { @@ -2502,10 +2536,10 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, tupledata = scratchptr; /* check that the mutually exclusive flags are not both set */ - Assert(!(all_visible_cleared && all_frozen_set)); + Assert(!(clear_all_visible && all_frozen_set)); xlrec->flags = 0; - if (all_visible_cleared) + if (clear_all_visible) xlrec->flags = XLH_INSERT_ALL_VISIBLE_CLEARED; /* @@ -2577,7 +2611,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, XLogRegisterData(xlrec, tupledata - scratch.data); XLogRegisterBuffer(HEAP_MULTI_INSERT_BLKREF_HEAP, buffer, REGBUF_STANDARD | bufflags); - if (all_frozen_set) + if (all_frozen_set || vmbuffer_modified) XLogRegisterBuffer(HEAP_MULTI_INSERT_BLKREF_VM, vmbuffer, 0); XLogRegisterBufData(HEAP_MULTI_INSERT_BLKREF_HEAP, tupledata, @@ -2589,7 +2623,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, recptr = XLogInsert(RM_HEAP2_ID, info); PageSetLSN(page, recptr); - if (all_frozen_set) + if (all_frozen_set || vmbuffer_modified) { Assert(BufferIsDirty(vmbuffer)); PageSetLSN(BufferGetPage(vmbuffer), recptr); @@ -2598,7 +2632,11 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, END_CRIT_SECTION(); - if (all_frozen_set) + /* + * We locked vmbuffer if clear_all_visible was true regardless of + * whether or not we ended up modifying the vmbuffer. + */ + if (all_frozen_set || clear_all_visible) LockBuffer(vmbuffer, BUFFER_LOCK_UNLOCK); UnlockReleaseBuffer(buffer); @@ -2730,6 +2768,7 @@ heap_delete(Relation relation, const ItemPointerData *tid, BlockNumber block; Buffer buffer; Buffer vmbuffer = InvalidBuffer; + bool vmbuffer_modified = false; TransactionId new_xmax; uint16 new_infomask, new_infomask2; @@ -2737,7 +2776,7 @@ heap_delete(Relation relation, const ItemPointerData *tid, bool walLogical = (options & TABLE_DELETE_NO_LOGICAL) == 0; bool have_tuple_lock = false; bool iscombo; - bool all_visible_cleared = false; + bool clear_all_visible = false; HeapTuple old_key_tuple = NULL; /* replica identity of the tuple */ bool old_key_copied = false; @@ -2987,6 +3026,13 @@ l1: xid, LockTupleExclusive, true, &new_xmax, &new_infomask, &new_infomask2); + /* Lock the VM before entering the critical section */ + if (PageIsAllVisible(page)) + { + clear_all_visible = true; + LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE); + } + START_CRIT_SECTION(); /* @@ -2998,12 +3044,14 @@ l1: */ PageSetPrunable(page, xid); - if (PageIsAllVisible(page)) + if (clear_all_visible) { - all_visible_cleared = true; + /* It's possible the VM bits were already clear */ + if (visibilitymap_clear(relation->rd_locator, BufferGetBlockNumber(buffer), + vmbuffer, VISIBILITYMAP_VALID_BITS)) + vmbuffer_modified = true; + PageClearAllVisible(page); - visibilitymap_clear(relation->rd_locator, BufferGetBlockNumber(buffer), - vmbuffer, VISIBILITYMAP_VALID_BITS); } /* store transaction information of xact deleting the tuple */ @@ -3043,7 +3091,7 @@ l1: log_heap_new_cid(relation, &tp); xlrec.flags = 0; - if (all_visible_cleared) + if (clear_all_visible) xlrec.flags |= XLH_DELETE_ALL_VISIBLE_CLEARED; if (changingPart) xlrec.flags |= XLH_DELETE_IS_PARTITION_MOVE; @@ -3094,13 +3142,27 @@ l1: /* filtering by origin on a row level is much more efficient */ XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN); + if (vmbuffer_modified) + XLogRegisterBuffer(HEAP_DELETE_BLKREF_VM, vmbuffer, 0); + recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE); PageSetLSN(page, recptr); + + if (vmbuffer_modified) + PageSetLSN(BufferGetPage(vmbuffer), recptr); } END_CRIT_SECTION(); + /* + * Release VM lock first, since it covers many heap blocks. We locked + * vmbuffer if clear_all_visible was true regardless of whether or not we + * ended up modifying the vmbuffer. + */ + if (clear_all_visible) + LockBuffer(vmbuffer, BUFFER_LOCK_UNLOCK); + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); if (vmbuffer != InvalidBuffer) @@ -3229,6 +3291,8 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, newbuf, vmbuffer = InvalidBuffer, vmbuffer_new = InvalidBuffer; + bool unlock_vmbuffer = false; + bool unlock_vmbuffer_new = false; bool need_toast; Size newtupsize, pagefree; @@ -3237,8 +3301,10 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, bool use_hot_update = false; bool summarized_update = false; bool key_intact; - bool all_visible_cleared = false; - bool all_visible_cleared_new = false; + bool clear_all_visible = false; + bool clear_all_visible_new = false; + bool vmbuffer_modified = false; + bool vmbuffer_new_modified = false; bool checked_lockers; bool locker_remains; bool id_has_external = false; @@ -3813,6 +3879,12 @@ l2: Assert(HEAP_XMAX_IS_LOCKED_ONLY(infomask_lock_old_tuple)); + if (PageIsAllVisible(page)) + { + LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE); + unlock_vmbuffer = true; + } + START_CRIT_SECTION(); /* Clear obsolete visibility flags ... */ @@ -3835,10 +3907,13 @@ l2: * overhead would be unchanged, that doesn't seem necessarily * worthwhile. */ - if (PageIsAllVisible(page) && - visibilitymap_clear(relation->rd_locator, block, vmbuffer, - VISIBILITYMAP_ALL_FROZEN)) - cleared_all_frozen = true; + if (PageIsAllVisible(page)) + { + /* It's possible all-frozen was already clear */ + if (visibilitymap_clear(relation->rd_locator, block, vmbuffer, + VISIBILITYMAP_ALL_FROZEN)) + cleared_all_frozen = true; + } MarkBufferDirty(buffer); @@ -3857,12 +3932,24 @@ l2: xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0; XLogRegisterData(&xlrec, SizeOfHeapLock); + + if (cleared_all_frozen) + XLogRegisterBuffer(HEAP_LOCK_BLKREF_VM, vmbuffer, 0); + recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK); PageSetLSN(page, recptr); + + if (cleared_all_frozen) + PageSetLSN(BufferGetPage(vmbuffer), recptr); } END_CRIT_SECTION(); + /* release VM lock first, since it covers many heap blocks */ + if (unlock_vmbuffer) + LockBuffer(vmbuffer, BUFFER_LOCK_UNLOCK); + unlock_vmbuffer = false; + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); /* @@ -4013,6 +4100,69 @@ l2: id_has_external, &old_key_copied); + clear_all_visible = PageIsAllVisible(page); + clear_all_visible_new = newbuf != buffer && PageIsAllVisible(newpage); + + /* + * Clear PD_ALL_VISIBLE flags and reset visibility map bits for any heap + * pages that were all-visible. If there are two heap pages, we may need + * to clear VM bits for both. + */ + if (clear_all_visible && clear_all_visible_new && + vmbuffer_new == vmbuffer) + { + /* + * This is the more complicated case: both the new and old heap pages + * are all-visible and both their VM bits are on the same page of the + * VM, so we register a single VM buffer as HEAP_UPDATE_BLKREF_VM_NEW + * in the WAL record. We must be careful to only lock and register one + * buffer, even though we modify it twice -- once for each heap + * block's VM bits. + */ + LockBuffer(vmbuffer_new, BUFFER_LOCK_EXCLUSIVE); + unlock_vmbuffer_new = true; + + /* We will not lock or attempt to modify old VM buffer */ + } + else + { + /* + * In all the remaining cases, we will clear at most one heap block's + * VM bits per VM page. + */ + Buffer vmbuffers[2] = { + clear_all_visible ? vmbuffer : InvalidBuffer, + clear_all_visible_new ? vmbuffer_new : InvalidBuffer + }; + + /* + * When both pages need different VM pages cleared, acquire the VM + * buffer locks in VM block order to avoid deadlocks between backends + * updating tuples in opposite directions across VM pages. + */ + if (clear_all_visible && clear_all_visible_new && + BufferGetBlockNumber(vmbuffers[0]) > BufferGetBlockNumber(vmbuffers[1])) + { + Buffer swap = vmbuffers[0]; + + vmbuffers[0] = vmbuffers[1]; + vmbuffers[1] = swap; + } + + Assert((!BufferIsValid(vmbuffers[0]) && !BufferIsValid(vmbuffers[1])) || + vmbuffers[0] != vmbuffers[1]); + + if (BufferIsValid(vmbuffers[0])) + LockBuffer(vmbuffers[0], BUFFER_LOCK_EXCLUSIVE); + if (BufferIsValid(vmbuffers[1])) + LockBuffer(vmbuffers[1], BUFFER_LOCK_EXCLUSIVE); + + if (clear_all_visible) + unlock_vmbuffer = true; + if (clear_all_visible_new) + unlock_vmbuffer_new = true; + } + /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); @@ -4063,20 +4213,42 @@ l2: /* record address of new tuple in t_ctid of old one */ oldtup.t_data->t_ctid = heaptup->t_self; - /* clear PD_ALL_VISIBLE flags, reset all visibilitymap bits */ - if (PageIsAllVisible(page)) + /* + * Clear PD_ALL_VISIBLE flags and reset all visibilitymap bits. In all + * cases, it's possible that PD_ALL_VISIBLE was set but the corresponding + * visibility map bits were already clear. + */ + if (clear_all_visible) { - all_visible_cleared = true; + if (visibilitymap_clear(relation->rd_locator, block, + vmbuffer, VISIBILITYMAP_VALID_BITS)) + { + /* + * When old and new heap blocks' VM bits are on the same VM page, + * that page is registered in the WAL record only once. If both + * heap pages were PD_ALL_VISIBLE and either VM bit needs + * clearing, we register the VM buffer as + * HEAP_UPDATE_BLKREF_VM_NEW. + */ + if (clear_all_visible_new && vmbuffer == vmbuffer_new) + vmbuffer_new_modified = true; + else + vmbuffer_modified = true; + } + PageClearAllVisible(page); - visibilitymap_clear(relation->rd_locator, BufferGetBlockNumber(buffer), - vmbuffer, VISIBILITYMAP_VALID_BITS); } - if (newbuf != buffer && PageIsAllVisible(newpage)) + if (clear_all_visible_new) { - all_visible_cleared_new = true; + /* + * If both heap blocks' VM bits are on the same VM buffer, this will + * clear the new heap block's VM bits from the shared vmbuffer. + */ + if (visibilitymap_clear(relation->rd_locator, BufferGetBlockNumber(newbuf), + vmbuffer_new, VISIBILITYMAP_VALID_BITS)) + vmbuffer_new_modified = true; + PageClearAllVisible(newpage); - visibilitymap_clear(relation->rd_locator, BufferGetBlockNumber(newbuf), - vmbuffer_new, VISIBILITYMAP_VALID_BITS); } if (newbuf != buffer) @@ -4099,20 +4271,33 @@ l2: } recptr = log_heap_update(relation, buffer, - newbuf, &oldtup, heaptup, + vmbuffer_modified ? vmbuffer : InvalidBuffer, + newbuf, + vmbuffer_new_modified ? vmbuffer_new : InvalidBuffer, + &oldtup, heaptup, old_key_tuple, - all_visible_cleared, - all_visible_cleared_new, + clear_all_visible, + clear_all_visible_new, walLogical); if (newbuf != buffer) { PageSetLSN(newpage, recptr); } PageSetLSN(page, recptr); + + if (vmbuffer_modified) + PageSetLSN(BufferGetPage(vmbuffer), recptr); + if (vmbuffer_new_modified) + PageSetLSN(BufferGetPage(vmbuffer_new), recptr); } END_CRIT_SECTION(); + if (unlock_vmbuffer) + LockBuffer(vmbuffer, BUFFER_LOCK_UNLOCK); + if (unlock_vmbuffer_new) + LockBuffer(vmbuffer_new, BUFFER_LOCK_UNLOCK); + if (newbuf != buffer) LockBuffer(newbuf, BUFFER_LOCK_UNLOCK); LockBuffer(buffer, BUFFER_LOCK_UNLOCK); @@ -4550,6 +4735,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple, ItemId lp; Page page; Buffer vmbuffer = InvalidBuffer; + bool unlock_vmbuffer = false; BlockNumber block; TransactionId xid, xmax; @@ -4563,6 +4749,7 @@ heap_lock_tuple(Relation relation, HeapTuple tuple, *buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid)); block = ItemPointerGetBlockNumber(tid); + page = BufferGetPage(*buffer); /* * Before locking the buffer, pin the visibility map page if it appears to @@ -4570,12 +4757,11 @@ heap_lock_tuple(Relation relation, HeapTuple tuple, * in the middle of changing this, so we'll need to recheck after we have * the lock. */ - if (PageIsAllVisible(BufferGetPage(*buffer))) + if (PageIsAllVisible(page)) visibilitymap_pin(relation, block, &vmbuffer); LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); - page = BufferGetPage(*buffer); lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid)); Assert(ItemIdIsNormal(lp)); @@ -5130,6 +5316,13 @@ failed: GetCurrentTransactionId(), mode, false, &xid, &new_infomask, &new_infomask2); + /* Lock VM buffer before entering critical section */ + if (PageIsAllVisible(page)) + { + LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE); + unlock_vmbuffer = true; + } + START_CRIT_SECTION(); /* @@ -5161,10 +5354,12 @@ failed: tuple->t_data->t_ctid = *tid; /* Clear only the all-frozen bit on visibility map if needed */ - if (PageIsAllVisible(page) && - visibilitymap_clear(relation->rd_locator, block, vmbuffer, - VISIBILITYMAP_ALL_FROZEN)) - cleared_all_frozen = true; + if (PageIsAllVisible(page)) + { + if (visibilitymap_clear(relation->rd_locator, block, vmbuffer, + VISIBILITYMAP_ALL_FROZEN)) + cleared_all_frozen = true; + } MarkBufferDirty(*buffer); @@ -5196,19 +5391,33 @@ failed: xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0; XLogRegisterData(&xlrec, SizeOfHeapLock); + if (cleared_all_frozen) + XLogRegisterBuffer(HEAP_LOCK_BLKREF_VM, vmbuffer, 0); + /* we don't decode row locks atm, so no need to log the origin */ recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK); PageSetLSN(page, recptr); + + if (cleared_all_frozen) + PageSetLSN(BufferGetPage(vmbuffer), recptr); } END_CRIT_SECTION(); + /* release VM lock first, since it covers many heap blocks */ + if (unlock_vmbuffer) + { + LockBuffer(vmbuffer, BUFFER_LOCK_UNLOCK); + unlock_vmbuffer = false; + } + result = TM_Ok; out_locked: LockBuffer(*buffer, BUFFER_LOCK_UNLOCK); + Assert(!unlock_vmbuffer); out_unlocked: if (BufferIsValid(vmbuffer)) @@ -5671,6 +5880,7 @@ heap_lock_updated_tuple_rec(Relation rel, TransactionId priorXmax, ItemPointerData tupid; HeapTupleData mytup; Buffer buf; + Page page; uint16 new_infomask, new_infomask2, old_infomask, @@ -5680,6 +5890,7 @@ heap_lock_updated_tuple_rec(Relation rel, TransactionId priorXmax, bool cleared_all_frozen = false; bool pinned_desired_page; Buffer vmbuffer = InvalidBuffer; + bool unlock_vmbuffer = false; BlockNumber block; ItemPointerCopy(tid, &tupid); @@ -5688,6 +5899,7 @@ heap_lock_updated_tuple_rec(Relation rel, TransactionId priorXmax, { new_infomask = 0; new_xmax = InvalidTransactionId; + cleared_all_frozen = false; block = ItemPointerGetBlockNumber(&tupid); ItemPointerCopy(&tupid, &(mytup.t_self)); @@ -5707,13 +5919,15 @@ heap_lock_updated_tuple_rec(Relation rel, TransactionId priorXmax, l4: CHECK_FOR_INTERRUPTS(); + page = BufferGetPage(buf); + /* * Before locking the buffer, pin the visibility map page if it * appears to be necessary. Since we haven't got the lock yet, * someone else might be in the middle of changing this, so we'll need * to recheck after we have the lock. */ - if (PageIsAllVisible(BufferGetPage(buf))) + if (PageIsAllVisible(page)) { visibilitymap_pin(rel, block, &vmbuffer); pinned_desired_page = true; @@ -5734,7 +5948,7 @@ l4: * this page. If this page isn't all-visible, we won't use the vm * page, but we hold onto such a pin till the end of the function. */ - if (!pinned_desired_page && PageIsAllVisible(BufferGetPage(buf))) + if (!pinned_desired_page && PageIsAllVisible(page)) { LockBuffer(buf, BUFFER_LOCK_UNLOCK); visibilitymap_pin(rel, block, &vmbuffer); @@ -5915,10 +6129,11 @@ l4: xid, mode, false, &new_xmax, &new_infomask, &new_infomask2); - if (PageIsAllVisible(BufferGetPage(buf)) && - visibilitymap_clear(rel->rd_locator, block, vmbuffer, - VISIBILITYMAP_ALL_FROZEN)) - cleared_all_frozen = true; + if (PageIsAllVisible(page)) + { + LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE); + unlock_vmbuffer = true; + } START_CRIT_SECTION(); @@ -5931,12 +6146,19 @@ l4: MarkBufferDirty(buf); + if (PageIsAllVisible(page)) + { + /* It's possible all-frozen was already clear */ + if (visibilitymap_clear(rel->rd_locator, block, vmbuffer, + VISIBILITYMAP_ALL_FROZEN)) + cleared_all_frozen = true; + } + /* XLOG stuff */ if (RelationNeedsWAL(rel)) { xl_heap_lock_updated xlrec; XLogRecPtr recptr; - Page page = BufferGetPage(buf); XLogBeginInsert(); XLogRegisterBuffer(HEAP_LOCK_BLKREF_HEAP, buf, REGBUF_STANDARD); @@ -5949,13 +6171,26 @@ l4: XLogRegisterData(&xlrec, SizeOfHeapLockUpdated); + if (cleared_all_frozen) + XLogRegisterBuffer(HEAP_LOCK_BLKREF_VM, vmbuffer, 0); + recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOCK_UPDATED); PageSetLSN(page, recptr); + + if (cleared_all_frozen) + PageSetLSN(BufferGetPage(vmbuffer), recptr); } END_CRIT_SECTION(); + /* release VM lock first, since it covers many heap blocks */ + if (unlock_vmbuffer) + { + LockBuffer(vmbuffer, BUFFER_LOCK_UNLOCK); + unlock_vmbuffer = false; + } + next: /* if we find the end of update chain, we're done. */ if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID || @@ -5981,6 +6216,7 @@ out_locked: out_unlocked: if (vmbuffer != InvalidBuffer) ReleaseBuffer(vmbuffer); + Assert(!unlock_vmbuffer); return result; } @@ -8776,8 +9012,9 @@ bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate) * have modified the buffer(s) and marked them dirty. */ static XLogRecPtr -log_heap_update(Relation reln, Buffer oldbuf, - Buffer newbuf, HeapTuple oldtup, HeapTuple newtup, +log_heap_update(Relation reln, Buffer oldbuf, Buffer vmbuffer_old, + Buffer newbuf, Buffer vmbuffer_new, + HeapTuple oldtup, HeapTuple newtup, HeapTuple old_key_tuple, bool all_visible_cleared, bool new_all_visible_cleared, bool walLogical) @@ -8987,6 +9224,21 @@ log_heap_update(Relation reln, Buffer oldbuf, old_key_tuple->t_len - SizeofHeapTupleHeader); } + /* + * Register VM buffers. If the old and new heap pages' VM bits are on the + * same VM page and both their VM bits were cleared, the caller passes + * only vmbuffer_new (mirroring the heap page convention where block 0 = + * new is always registered). + */ + Assert((BufferIsInvalid(vmbuffer_old) && BufferIsInvalid(vmbuffer_new)) || + (vmbuffer_old != vmbuffer_new)); + + if (BufferIsValid(vmbuffer_new)) + XLogRegisterBuffer(HEAP_UPDATE_BLKREF_VM_NEW, vmbuffer_new, 0); + + if (BufferIsValid(vmbuffer_old)) + XLogRegisterBuffer(HEAP_UPDATE_BLKREF_VM_OLD, vmbuffer_old, 0); + /* filtering by origin on a row level is much more efficient */ XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN); diff --git a/src/backend/access/heap/heapam_xlog.c b/src/backend/access/heap/heapam_xlog.c index 31ff6aea282..7a7bc7ea740 100644 --- a/src/backend/access/heap/heapam_xlog.c +++ b/src/backend/access/heap/heapam_xlog.c @@ -22,6 +22,46 @@ #include "storage/freespace.h" #include "storage/standby.h" +/* + * Clear visibility map bits for a single heap block during heap redo. + * + * Used by records that modify one heap block and, at most, its corresponding + * VM block (insert, delete, multi_insert, lock). Records that can touch + * multiple heap or VM blocks (e.g. updates) replay the VM changes inline + * instead. + * + * 'record' is the WAL record being replayed + * 'target_locator' identifies the relation whose VM is being updated + * 'heap_blkno' is the heap block whose VM bits should be cleared + * 'wal_vm_block_id' is the WAL block reference id of the VM page + * 'flags' specifies which visibility map bits to clear + */ +static void +heap_xlog_vm_clear(XLogReaderState *record, + RelFileLocator target_locator, + BlockNumber heap_blkno, + uint8 wal_vm_block_id, uint8 flags) +{ + XLogRecPtr lsn = record->EndRecPtr; + Buffer vmbuffer = InvalidBuffer; + + if (!XLogRecHasBlockRef(record, wal_vm_block_id)) + return; + + /* + * If the vmbuffer was registered, use the recovery-specific routines to + * read it. These will either apply an FPI or indicate that we should + * clear the requested bits ourselves. + */ + if (XLogReadBufferForRedo(record, wal_vm_block_id, + &vmbuffer) == BLK_NEEDS_REDO) + { + if (visibilitymap_clear(target_locator, heap_blkno, vmbuffer, flags)) + PageSetLSN(BufferGetPage(vmbuffer), lsn); + } + if (BufferIsValid(vmbuffer)) + UnlockReleaseBuffer(vmbuffer); +} /* * Replay XLOG_HEAP2_PRUNE_* records. @@ -309,15 +349,9 @@ heap_xlog_delete(XLogReaderState *record) * already up-to-date. */ if (xlrec->flags & XLH_DELETE_ALL_VISIBLE_CLEARED) - { - Relation reln = CreateFakeRelcacheEntry(target_locator); - Buffer vmbuffer = InvalidBuffer; - - visibilitymap_pin(reln, blkno, &vmbuffer); - visibilitymap_clear(target_locator, blkno, vmbuffer, VISIBILITYMAP_VALID_BITS); - ReleaseBuffer(vmbuffer); - FreeFakeRelcacheEntry(reln); - } + heap_xlog_vm_clear(record, target_locator, + blkno, HEAP_DELETE_BLKREF_VM, + VISIBILITYMAP_VALID_BITS); if (XLogReadBufferForRedo(record, HEAP_DELETE_BLKREF_HEAP, &buffer) == BLK_NEEDS_REDO) @@ -398,15 +432,9 @@ heap_xlog_insert(XLogReaderState *record) * already up-to-date. */ if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) - { - Relation reln = CreateFakeRelcacheEntry(target_locator); - Buffer vmbuffer = InvalidBuffer; - - visibilitymap_pin(reln, blkno, &vmbuffer); - visibilitymap_clear(target_locator, blkno, vmbuffer, VISIBILITYMAP_VALID_BITS); - ReleaseBuffer(vmbuffer); - FreeFakeRelcacheEntry(reln); - } + heap_xlog_vm_clear(record, target_locator, + blkno, HEAP_INSERT_BLKREF_VM, + VISIBILITYMAP_VALID_BITS); /* * If we inserted the first and only tuple on the page, re-initialize the @@ -530,17 +558,15 @@ heap_xlog_multi_insert(XLogReaderState *record) /* * The visibility map may need to be fixed even if the heap page is * already up-to-date. + * + * Clear the VM (if needed) before clearing the heap page-level visibility + * flag (PD_ALL_VISIBLE) to prevent the heap page from being marked + * all-visible in the VM while its PD_ALL_VISIBLE is clear. */ if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) - { - Relation reln = CreateFakeRelcacheEntry(rlocator); - - visibilitymap_pin(reln, blkno, &vmbuffer); - visibilitymap_clear(rlocator, blkno, vmbuffer, VISIBILITYMAP_VALID_BITS); - ReleaseBuffer(vmbuffer); - vmbuffer = InvalidBuffer; - FreeFakeRelcacheEntry(reln); - } + heap_xlog_vm_clear(record, rlocator, + blkno, HEAP_MULTI_INSERT_BLKREF_VM, + VISIBILITYMAP_VALID_BITS); if (isinit) { @@ -640,7 +666,7 @@ heap_xlog_multi_insert(XLogReaderState *record) buffer = InvalidBuffer; /* - * Read and update the visibility map (VM) block. + * Read and update the visibility map (VM) block to set it frozen. * * We must always redo VM changes, even if the corresponding heap page * update was skipped due to the LSN interlock. Each VM block covers @@ -713,6 +739,8 @@ heap_xlog_update(XLogReaderState *record, bool hot_update) nbuffer; Page opage, npage; + bool has_vm_old, + has_vm_new; OffsetNumber offnum; ItemId lp; HeapTupleData oldtup; @@ -752,25 +780,74 @@ heap_xlog_update(XLogReaderState *record, bool hot_update) * The visibility map may need to be fixed even if the heap page is * already up-to-date. */ - if (xlrec->flags & XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED) - { - Relation reln = CreateFakeRelcacheEntry(rlocator); - Buffer vmbuffer = InvalidBuffer; + has_vm_old = XLogRecHasBlockRef(record, HEAP_UPDATE_BLKREF_VM_OLD); + has_vm_new = XLogRecHasBlockRef(record, HEAP_UPDATE_BLKREF_VM_NEW); - visibilitymap_pin(reln, oldblk, &vmbuffer); - visibilitymap_clear(rlocator, oldblk, vmbuffer, VISIBILITYMAP_VALID_BITS); - ReleaseBuffer(vmbuffer); - FreeFakeRelcacheEntry(reln); + if (has_vm_new) + { + Buffer vmbuffer_new = InvalidBuffer; + + Assert(xlrec->flags & XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED); + + if (XLogReadBufferForRedo(record, HEAP_UPDATE_BLKREF_VM_NEW, + &vmbuffer_new) == BLK_NEEDS_REDO) + { + /* + * If both the old and new heap pages were all-visible and their + * VM bits are on the same VM page, that single VM page is + * registered as HEAP_UPDATE_BLKREF_VM_NEW. Clear both heap + * blocks' VM bits from the single provided VM buffer. It's + * possible that one of the page's VM bits were already clear, but + * visibilitymap_clear() is harmless as long as we provide it the + * correct bits. + * + * We must verify that oldblk's VM bits really are on this VM + * page, rather than relying on the absence of a separate VM_OLD + * block reference: VM_OLD is also omitted when oldblk is on a + * different VM page but its bit was already clear. + */ + if (xlrec->flags & XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED && + visibilitymap_pin_ok(oldblk, vmbuffer_new)) + { + if (visibilitymap_clear(rlocator, oldblk, vmbuffer_new, + VISIBILITYMAP_VALID_BITS)) + PageSetLSN(BufferGetPage(vmbuffer_new), lsn); + } + /* If VM_NEW is registered, we are sure newblk is on VM_NEW */ + if (visibilitymap_clear(rlocator, newblk, vmbuffer_new, + VISIBILITYMAP_VALID_BITS)) + PageSetLSN(BufferGetPage(vmbuffer_new), lsn); + } + if (BufferIsValid(vmbuffer_new)) + UnlockReleaseBuffer(vmbuffer_new); + } + if (has_vm_old) + { + Buffer vmbuffer_old = InvalidBuffer; + + Assert(xlrec->flags & XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED); + + if (XLogReadBufferForRedo(record, HEAP_UPDATE_BLKREF_VM_OLD, + &vmbuffer_old) == BLK_NEEDS_REDO) + { + if (visibilitymap_clear(rlocator, oldblk, vmbuffer_old, + VISIBILITYMAP_VALID_BITS)) + PageSetLSN(BufferGetPage(vmbuffer_old), lsn); + } + if (BufferIsValid(vmbuffer_old)) + UnlockReleaseBuffer(vmbuffer_old); } /* * In normal operation, it is important to lock the two pages in * page-number order, to avoid possible deadlocks against other update * operations going the other way. However, during WAL replay there can - * be no other update happening, so we don't need to worry about that. But - * we *do* need to worry that we don't expose an inconsistent state to Hot - * Standby queries --- so the original page can't be unlocked before we've - * added the new tuple to the new page. + * be no other update happening, so we don't need to worry about that. + * Notice we also don't worry about this when locking VM buffers above. + * + * But we *do* need to worry that we don't expose an inconsistent state to + * Hot Standby queries --- so the original page can't be unlocked before + * we've added the new tuple to the new page. */ /* Deal with old tuple version */ @@ -834,21 +911,6 @@ heap_xlog_update(XLogReaderState *record, bool hot_update) newaction = XLogReadBufferForRedo(record, HEAP_UPDATE_BLKREF_HEAP_NEW, &nbuffer); - /* - * The visibility map may need to be fixed even if the heap page is - * already up-to-date. - */ - if (xlrec->flags & XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED) - { - Relation reln = CreateFakeRelcacheEntry(rlocator); - Buffer vmbuffer = InvalidBuffer; - - visibilitymap_pin(reln, newblk, &vmbuffer); - visibilitymap_clear(rlocator, newblk, vmbuffer, VISIBILITYMAP_VALID_BITS); - ReleaseBuffer(vmbuffer); - FreeFakeRelcacheEntry(reln); - } - /* Deal with new tuple */ if (newaction == BLK_NEEDS_REDO) { @@ -1041,19 +1103,14 @@ heap_xlog_lock(XLogReaderState *record) if (xlrec->flags & XLH_LOCK_ALL_FROZEN_CLEARED) { RelFileLocator rlocator; - Buffer vmbuffer = InvalidBuffer; BlockNumber block; - Relation reln; XLogRecGetBlockTag(record, HEAP_LOCK_BLKREF_HEAP, &rlocator, NULL, &block); - reln = CreateFakeRelcacheEntry(rlocator); - visibilitymap_pin(reln, block, &vmbuffer); - visibilitymap_clear(rlocator, block, vmbuffer, VISIBILITYMAP_ALL_FROZEN); - - ReleaseBuffer(vmbuffer); - FreeFakeRelcacheEntry(reln); + heap_xlog_vm_clear(record, rlocator, + block, HEAP_LOCK_BLKREF_VM, + VISIBILITYMAP_ALL_FROZEN); } if (XLogReadBufferForRedo(record, HEAP_LOCK_BLKREF_HEAP, @@ -1119,19 +1176,14 @@ heap_xlog_lock_updated(XLogReaderState *record) if (xlrec->flags & XLH_LOCK_ALL_FROZEN_CLEARED) { RelFileLocator rlocator; - Buffer vmbuffer = InvalidBuffer; BlockNumber block; - Relation reln; XLogRecGetBlockTag(record, HEAP_LOCK_BLKREF_HEAP, &rlocator, NULL, &block); - reln = CreateFakeRelcacheEntry(rlocator); - visibilitymap_pin(reln, block, &vmbuffer); - visibilitymap_clear(rlocator, block, vmbuffer, VISIBILITYMAP_ALL_FROZEN); - - ReleaseBuffer(vmbuffer); - FreeFakeRelcacheEntry(reln); + heap_xlog_vm_clear(record, rlocator, + block, HEAP_LOCK_BLKREF_VM, + VISIBILITYMAP_ALL_FROZEN); } if (XLogReadBufferForRedo(record, HEAP_LOCK_BLKREF_HEAP, diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index 6cf134dfd27..85cdceb0524 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -947,9 +947,11 @@ heap_page_fix_vm_corruption(PruneState *prstate, OffsetNumber offnum, if (do_clear_vm) { + LockBuffer(prstate->vmbuffer, BUFFER_LOCK_EXCLUSIVE); visibilitymap_clear(prstate->relation->rd_locator, prstate->block, prstate->vmbuffer, VISIBILITYMAP_VALID_BITS); + LockBuffer(prstate->vmbuffer, BUFFER_LOCK_UNLOCK); prstate->old_vmbits = 0; } } diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 3b699291b23..1d50e41b629 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -139,22 +139,26 @@ static Buffer vm_readbuf(Relation rel, BlockNumber blkno, bool extend); static Buffer vm_extend(Relation rel, BlockNumber vm_nblocks); - /* * visibilitymap_clear - clear specified bits for one page in visibility map * - * 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. Returns true if any bits have been cleared and false otherwise. + * You must pass a buffer containing the correct map page to this function, + * which already needs to be pinned and locked exclusively. + * + * This function doesn't do any I/O. Returns true if any bits have been + * cleared and false otherwise. */ bool -visibilitymap_clear(RelFileLocator rlocator, - BlockNumber heapBlk, Buffer vmbuf, uint8 flags) +visibilitymap_clear(RelFileLocator rlocator, BlockNumber heapBlk, + Buffer vmbuf, uint8 flags) { - BlockNumber mapBlock = HEAPBLK_TO_MAPBLOCK(heapBlk); int mapByte = HEAPBLK_TO_MAPBYTE(heapBlk); int mapOffset = HEAPBLK_TO_OFFSET(heapBlk); +#ifdef USE_ASSERT_CHECKING + BlockNumber mapBlock = HEAPBLK_TO_MAPBLOCK(heapBlk); +#endif uint8 mask = flags << mapOffset; + Page page; char *map; bool cleared = false; @@ -168,11 +172,11 @@ visibilitymap_clear(RelFileLocator rlocator, heapBlk); #endif - if (!BufferIsValid(vmbuf) || BufferGetBlockNumber(vmbuf) != mapBlock) - elog(ERROR, "wrong buffer passed to visibilitymap_clear"); + Assert(BufferIsValid(vmbuf) && BufferGetBlockNumber(vmbuf) == mapBlock); + Assert(BufferIsLockedByMeInMode(vmbuf, BUFFER_LOCK_EXCLUSIVE)); - LockBuffer(vmbuf, BUFFER_LOCK_EXCLUSIVE); - map = PageGetContents(BufferGetPage(vmbuf)); + page = BufferGetPage(vmbuf); + map = PageGetContents(page); if (map[mapByte] & mask) { @@ -182,8 +186,6 @@ visibilitymap_clear(RelFileLocator rlocator, cleared = true; } - LockBuffer(vmbuf, BUFFER_LOCK_UNLOCK); - return cleared; } diff --git a/src/bin/pg_walsummary/t/002_blocks.pl b/src/bin/pg_walsummary/t/002_blocks.pl index f5fe94f9f15..bebc1137c0f 100644 --- a/src/bin/pg_walsummary/t/002_blocks.pl +++ b/src/bin/pg_walsummary/t/002_blocks.pl @@ -93,13 +93,14 @@ my $filename = sprintf "%s/pg_wal/summaries/%08s%08s%08s%08s%08s.summary", split(m@/@, $end_lsn); ok(-f $filename, "WAL summary file exists"); -# Run pg_walsummary on it. We expect exactly two blocks to be modified, -# block 0 and one other. +# Run pg_walsummary on it. We expect exactly three blocks to be modified, +# block 0 (old tuple), another block (new tuple), and the block for the VM. my ($stdout, $stderr) = run_command([ 'pg_walsummary', '-i', $filename ]); note($stdout); @lines = split(/\n/, $stdout); like($stdout, qr/FORK main: block 0$/m, "stdout shows block 0 modified"); +like($stdout, qr/FORK vm: block 0$/m, "stdout shows VM block 0 modified"); is($stderr, '', 'stderr is empty'); -is(0 + @lines, 2, "UPDATE modified 2 blocks"); +is(0 + @lines, 3, "UPDATE modified 3 blocks"); done_testing(); diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h index fedee7f3501..3f79c389a90 100644 --- a/src/include/access/heapam_xlog.h +++ b/src/include/access/heapam_xlog.h @@ -113,6 +113,7 @@ /* This is what we need to know about delete */ #define HEAP_DELETE_BLKREF_HEAP 0 +#define HEAP_DELETE_BLKREF_VM 1 typedef struct xl_heap_delete { @@ -162,6 +163,7 @@ typedef struct xl_heap_header /* This is what we need to know about insert */ #define HEAP_INSERT_BLKREF_HEAP 0 +#define HEAP_INSERT_BLKREF_VM 1 typedef struct xl_heap_insert { @@ -225,10 +227,22 @@ typedef struct xl_multi_insert_tuple * * HEAP_UPDATE_BLKREF_HEAP_OLD: old page, if different. (no data, just a reference * to the block) + * + * HEAP_UPDATE_BLKREF_VM_NEW: VM page covering the new heap page. Registered + * when XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED is set and the new heap page's VM bit + * was actually cleared. Also covers the old heap page's VM bits when both heap + * pages map to the same VM page and both blocks' VM bits were actually cleared. + * + * HEAP_UPDATE_BLKREF_VM_OLD: VM page covering the old heap page. Only + * registered when XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED is set and the old heap + * page's VM bits were actually cleared. Also only registered when the old heap + * page's VM bits are on a different VM page than the new heap page's or they + * are on the same VM page and only the old block's VM bits are cleared. */ - #define HEAP_UPDATE_BLKREF_HEAP_NEW 0 #define HEAP_UPDATE_BLKREF_HEAP_OLD 1 +#define HEAP_UPDATE_BLKREF_VM_NEW 2 +#define HEAP_UPDATE_BLKREF_VM_OLD 3 typedef struct xl_heap_update { @@ -417,6 +431,7 @@ typedef struct xlhp_prune_items /* This is what we need to know about lock */ #define HEAP_LOCK_BLKREF_HEAP 0 +#define HEAP_LOCK_BLKREF_VM 1 typedef struct xl_heap_lock { diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h index 55663e6f4af..be718993401 100644 --- a/src/include/access/xlog_internal.h +++ b/src/include/access/xlog_internal.h @@ -32,7 +32,7 @@ /* * Each page of XLOG file has a header like this: */ -#define XLOG_PAGE_MAGIC 0xD120 /* can be used as WAL version indicator */ +#define XLOG_PAGE_MAGIC 0xD121 /* can be used as WAL version indicator */ typedef struct XLogPageHeaderData {