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);
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) <=
*/
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,
+ ItemPointerGetBlockNumber(&(heaptup->t_self)),
+ vmbuffer, VISIBILITYMAP_VALID_BITS))
+ vmbuffer_modified = true;
+
PageClearAllVisible(page);
- visibilitymap_clear(relation,
- ItemPointerGetBlockNumber(&(heaptup->t_self)),
- vmbuffer, VISIBILITYMAP_VALID_BITS);
}
/*
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;
/* 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);
/*
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();
/* 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();
* 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,
+ BufferGetBlockNumber(buffer),
+ vmbuffer, VISIBILITYMAP_VALID_BITS))
+ vmbuffer_modified = true;
+
PageClearAllVisible(page);
- visibilitymap_clear(relation,
- BufferGetBlockNumber(buffer),
- vmbuffer, VISIBILITYMAP_VALID_BITS);
}
else if (all_frozen_set)
{
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;
/*
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,
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);
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);
BlockNumber block;
Buffer buffer;
Buffer vmbuffer = InvalidBuffer;
+ bool vmbuffer_modified = false;
TransactionId new_xmax;
uint16 new_infomask,
new_infomask2;
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;
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();
/*
*/
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, BufferGetBlockNumber(buffer),
+ vmbuffer, VISIBILITYMAP_VALID_BITS))
+ vmbuffer_modified = true;
+
PageClearAllVisible(page);
- visibilitymap_clear(relation, BufferGetBlockNumber(buffer),
- vmbuffer, VISIBILITYMAP_VALID_BITS);
}
/* store transaction information of xact deleting the tuple */
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;
/* 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)
newbuf,
vmbuffer = InvalidBuffer,
vmbuffer_new = InvalidBuffer;
+ bool unlock_vmbuffer = false;
+ bool unlock_vmbuffer_new = false;
bool need_toast;
Size newtupsize,
pagefree;
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;
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 ... */
* overhead would be unchanged, that doesn't seem necessarily
* worthwhile.
*/
- if (PageIsAllVisible(page) &&
- visibilitymap_clear(relation, block, vmbuffer,
- VISIBILITYMAP_ALL_FROZEN))
- cleared_all_frozen = true;
+ if (PageIsAllVisible(page))
+ {
+ /* It's possible all-frozen was already clear */
+ if (visibilitymap_clear(relation, block, vmbuffer,
+ VISIBILITYMAP_ALL_FROZEN))
+ cleared_all_frozen = true;
+ }
MarkBufferDirty(buffer);
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);
/*
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();
/* 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, 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, 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, BufferGetBlockNumber(newbuf),
+ vmbuffer_new, VISIBILITYMAP_VALID_BITS))
+ vmbuffer_new_modified = true;
+
PageClearAllVisible(newpage);
- visibilitymap_clear(relation, BufferGetBlockNumber(newbuf),
- vmbuffer_new, VISIBILITYMAP_VALID_BITS);
}
if (newbuf != buffer)
}
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);
ItemId lp;
Page page;
Buffer vmbuffer = InvalidBuffer;
+ bool unlock_vmbuffer = false;
BlockNumber block;
TransactionId xid,
xmax;
*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
block = ItemPointerGetBlockNumber(tid);
+ page = BufferGetPage(*buffer);
/*
* Before locking the buffer, pin the visibility map page if it appears to
* 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));
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();
/*
tuple->t_data->t_ctid = *tid;
/* Clear only the all-frozen bit on visibility map if needed */
- if (PageIsAllVisible(page) &&
- visibilitymap_clear(relation, block, vmbuffer,
- VISIBILITYMAP_ALL_FROZEN))
- cleared_all_frozen = true;
-
+ if (PageIsAllVisible(page))
+ {
+ if (visibilitymap_clear(relation, block, vmbuffer,
+ VISIBILITYMAP_ALL_FROZEN))
+ cleared_all_frozen = true;
+ }
MarkBufferDirty(*buffer);
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))
ItemPointerData tupid;
HeapTupleData mytup;
Buffer buf;
+ Page page;
uint16 new_infomask,
new_infomask2,
old_infomask,
bool cleared_all_frozen = false;
bool pinned_desired_page;
Buffer vmbuffer = InvalidBuffer;
+ bool unlock_vmbuffer = false;
BlockNumber block;
ItemPointerCopy(tid, &tupid);
{
new_infomask = 0;
new_xmax = InvalidTransactionId;
+ cleared_all_frozen = false;
block = ItemPointerGetBlockNumber(&tupid);
ItemPointerCopy(&tupid, &(mytup.t_self));
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;
* 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);
xid, mode, false,
&new_xmax, &new_infomask, &new_infomask2);
- if (PageIsAllVisible(BufferGetPage(buf)) &&
- visibilitymap_clear(rel, block, vmbuffer,
- VISIBILITYMAP_ALL_FROZEN))
- cleared_all_frozen = true;
+ if (PageIsAllVisible(page))
+ {
+ LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE);
+ unlock_vmbuffer = true;
+ }
START_CRIT_SECTION();
MarkBufferDirty(buf);
+ if (PageIsAllVisible(page))
+ {
+ /* It's possible all-frozen was already clear */
+ if (visibilitymap_clear(rel, 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);
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 ||
out_unlocked:
if (vmbuffer != InvalidBuffer)
ReleaseBuffer(vmbuffer);
+ Assert(!unlock_vmbuffer);
return result;
}
* 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)
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);
#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;
+ Relation reln = CreateFakeRelcacheEntry(target_locator);
+ Buffer vmbuffer = InvalidBuffer;
+
+ /*
+ * 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 (XLogRecHasBlockRef(record, wal_vm_block_id))
+ {
+ if (XLogReadBufferForRedo(record, wal_vm_block_id,
+ &vmbuffer) == BLK_NEEDS_REDO)
+ {
+ if (visibilitymap_clear(reln, heap_blkno, vmbuffer, flags))
+ PageSetLSN(BufferGetPage(vmbuffer), lsn);
+ }
+ if (BufferIsValid(vmbuffer))
+ UnlockReleaseBuffer(vmbuffer);
+ }
+
+ FreeFakeRelcacheEntry(reln);
+}
/*
* Replay XLOG_HEAP2_PRUNE_* records.
* 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(reln, 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)
* 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(reln, 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
/*
* 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(reln, 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)
{
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
nbuffer;
Page opage,
npage;
+ bool has_vm_old,
+ has_vm_new;
OffsetNumber offnum;
ItemId lp;
HeapTupleData oldtup;
Size freespace = 0;
XLogRedoAction oldaction;
XLogRedoAction newaction;
+ Relation reln = NULL;
/* initialize to keep the compiler quiet */
oldtup.t_data = NULL;
* 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)
+ has_vm_old = XLogRecHasBlockRef(record, HEAP_UPDATE_BLKREF_VM_OLD);
+ has_vm_new = XLogRecHasBlockRef(record, HEAP_UPDATE_BLKREF_VM_NEW);
+
+ if (has_vm_new || has_vm_old)
+ reln = CreateFakeRelcacheEntry(rlocator);
+
+ if (has_vm_new)
{
- Relation reln = CreateFakeRelcacheEntry(rlocator);
- Buffer vmbuffer = InvalidBuffer;
+ Buffer vmbuffer_new = InvalidBuffer;
- visibilitymap_pin(reln, oldblk, &vmbuffer);
- visibilitymap_clear(reln, oldblk, vmbuffer, VISIBILITYMAP_VALID_BITS);
- ReleaseBuffer(vmbuffer);
- FreeFakeRelcacheEntry(reln);
+ 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(reln, 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(reln, 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(reln, oldblk, vmbuffer_old,
+ VISIBILITYMAP_VALID_BITS))
+ PageSetLSN(BufferGetPage(vmbuffer_old), lsn);
+ }
+ if (BufferIsValid(vmbuffer_old))
+ UnlockReleaseBuffer(vmbuffer_old);
+ }
+
+ if (reln)
+ FreeFakeRelcacheEntry(reln);
/*
* 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 */
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(reln, newblk, vmbuffer, VISIBILITYMAP_VALID_BITS);
- ReleaseBuffer(vmbuffer);
- FreeFakeRelcacheEntry(reln);
- }
-
/* Deal with new tuple */
if (newaction == BLK_NEEDS_REDO)
{
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(reln, 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,
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(reln, 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,