v36-0007-Eliminate-XLOG_HEAP2_VISIBLE-from-vacuum-phase-I.patch
text/x-patch
Filename: v36-0007-Eliminate-XLOG_HEAP2_VISIBLE-from-vacuum-phase-I.patch
Type: text/x-patch
Part: 6
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: format-patch
Series: patch v36-0007
Subject: Eliminate XLOG_HEAP2_VISIBLE from vacuum phase I prune/freeze
| File | + | − |
|---|---|---|
| src/backend/access/heap/pruneheap.c | 247 | 78 |
| src/backend/access/heap/vacuumlazy.c | 9 | 98 |
| src/include/access/heapam.h | 15 | 23 |
From a96825f9026c4e9d8c8f55633b0e6dcf6f83c156 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Tue, 2 Dec 2025 15:07:42 -0500
Subject: [PATCH v36 07/16] Eliminate XLOG_HEAP2_VISIBLE from vacuum phase I
prune/freeze
Vacuum no longer emits a separate WAL record for each page set
all-visible or all-frozen during phase I. Instead, visibility map
updates are now included in the XLOG_HEAP2_PRUNE_VACUUM_SCAN record that
is already emitted for pruning and freezing.
Previously, heap_page_prune_and_freeze() determined whether a page was
all-visible, but the corresponding VM bits were only set later in
lazy_scan_prune(). Now the VM is updated immediately in
heap_page_prune_and_freeze(), at the same time as the heap
modifications.
This change applies only to vacuum phase I, not to pruning performed
during normal page access.
Author: Melanie Plageman <melanieplageman@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Reviewed-by: Kirill Reshke <reshkekirill@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/flat/CAAKRu_ZMw6Npd_qm2KM%2BFwQ3cMOMx1Dh3VMhp8-V7SOLxdK9-g%40mail.gmail.com
---
src/backend/access/heap/pruneheap.c | 325 ++++++++++++++++++++-------
src/backend/access/heap/vacuumlazy.c | 107 +--------
src/include/access/heapam.h | 38 ++--
3 files changed, 271 insertions(+), 199 deletions(-)
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 0de14a468f6..ec58f717c0b 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -72,6 +72,21 @@ typedef struct
OffsetNumber nowunused[MaxHeapTuplesPerPage];
HeapTupleFreeze frozen[MaxHeapTuplesPerPage];
+ /*
+ * set_all_visible and set_all_frozen indicate if the all-visible and
+ * all-frozen bits in the visibility map can be set for this page after
+ * pruning.
+ *
+ * NOTE: set_all_visible and set_all_frozen initially don't include
+ * LP_DEAD items. That's convenient for heap_page_prune_and_freeze() to
+ * use them to decide whether to opportunistically freeze the page or not.
+ * The set_all_visible and set_all_frozen values ultimately used to set
+ * the VM are adjusted to include LP_DEAD items after we determine whether
+ * or not to opportunistically freeze.
+ */
+ bool set_all_visible;
+ bool set_all_frozen;
+
/*-------------------------------------------------------
* Working state for HOT chain processing
*-------------------------------------------------------
@@ -129,12 +144,16 @@ typedef struct
/*
* Caller must provide a pinned vmbuffer corresponding to the heap block
* passed to heap_page_prune_and_freeze(). We will fix any corruption
- * found in the VM.
+ * found in the VM and set the VM if the page is all-visible/all-frozen.
*/
Buffer vmbuffer;
- /* Bits in the vmbuffer for this heap page */
- uint8 vmbits;
+ /*
+ * The state of the VM bits at the beginning of pruning and the state they
+ * will be in at the end.
+ */
+ uint8 old_vmbits;
+ uint8 new_vmbits;
/* The newest xmin of live tuples on the page */
TransactionId newest_live_xid;
@@ -164,21 +183,6 @@ typedef struct
*/
int lpdead_items; /* number of items in the array */
OffsetNumber *deadoffsets; /* points directly to presult->deadoffsets */
-
- /*
- * set_all_visible and set_all_frozen indicate if the all-visible and
- * all-frozen bits in the visibility map can be set for this page after
- * pruning.
- *
- * NOTE: set_all_visible and set_all_frozen initially don't include
- * LP_DEAD items. That's convenient for heap_page_prune_and_freeze() to
- * use them to decide whether to freeze the page or not. The
- * set_all_visible and set_all_frozen values returned to the caller are
- * adjusted to include LP_DEAD items after we determine whether to
- * opportunistically freeze.
- */
- bool set_all_visible;
- bool set_all_frozen;
} PruneState;
/* Local functions */
@@ -216,6 +220,12 @@ static void page_verify_redirects(Page page);
static bool heap_page_will_freeze(bool did_tuple_hint_fpi, bool do_prune, bool do_hint_prune,
PruneState *prstate);
+static bool heap_page_will_set_vm(PruneState *prstate, PruneReason reason);
+static TransactionId get_conflict_xid(bool do_prune, bool do_freeze, bool do_set_vm,
+ uint8 old_vmbits, uint8 new_vmbits,
+ TransactionId latest_xid_removed,
+ TransactionId newest_frozen_xid,
+ TransactionId newest_live_xid);
/*
@@ -382,9 +392,10 @@ prune_freeze_setup(PruneFreezeParams *params,
Assert(BufferIsValid(params->vmbuffer));
prstate->vmbuffer = params->vmbuffer;
- prstate->vmbits = visibilitymap_get_status(prstate->relation,
- prstate->block,
- &prstate->vmbuffer);
+ prstate->new_vmbits = 0;
+ prstate->old_vmbits = visibilitymap_get_status(prstate->relation,
+ prstate->block,
+ &prstate->vmbuffer);
/*
* Our strategy is to scan the page and make lists of items to change,
@@ -783,6 +794,66 @@ heap_page_will_freeze(bool did_tuple_hint_fpi,
return do_freeze;
}
+/*
+ * Calculate the conflict horizon for the whole XLOG_HEAP2_PRUNE_VACUUM_SCAN
+ * or XLOG_HEAP2_PRUNE_ON_ACCESS record.
+ */
+static TransactionId
+get_conflict_xid(bool do_prune, bool do_freeze, bool do_set_vm,
+ uint8 old_vmbits, uint8 new_vmbits,
+ TransactionId latest_xid_removed,
+ TransactionId newest_frozen_xid,
+ TransactionId newest_live_xid)
+{
+ TransactionId conflict_xid = InvalidTransactionId;
+
+ /*
+ * We can omit the snapshot conflict horizon if we are not pruning or
+ * freezing any tuples and are setting an already all-visible page
+ * all-frozen in the VM. In this case, all of the tuples on the page must
+ * already be seen as frozen by all MVCC snapshots on the standby (any
+ * conflict would have been handled in reaction to the WAL record freezing
+ * those tuples).
+ */
+ if (!do_prune &&
+ !do_freeze &&
+ (old_vmbits & VISIBILITYMAP_ALL_VISIBLE) &&
+ (new_vmbits & VISIBILITYMAP_ALL_FROZEN))
+ return InvalidTransactionId;
+
+ /*
+ * The snapshot conflict horizon for the whole record should be the most
+ * conservative (newest) of all the horizons calculated for any of the
+ * possible modifications. If this record will prune tuples, any queries
+ * on the standby with xmin older than the youngest XID of the most
+ * recently removed tuple this record will prune will conflict. If this
+ * record will freeze tuples, any queries on the standby with xmin older
+ * than the youngest tuple this record will freeze will conflict.
+ *
+ * If we are setting the VM, the conflict horizon is almost always the
+ * newest live XID, except in the situation described above.
+ *
+ * By picking the newest of all of those, we can ensure that all changes
+ * in the record have been taken into account.
+ */
+ if (do_set_vm)
+ conflict_xid = newest_live_xid;
+ if (do_freeze && TransactionIdFollows(newest_frozen_xid, conflict_xid))
+ conflict_xid = newest_frozen_xid;
+
+ /*
+ * If we are removing tuples with a younger XID than our so far calculated
+ * conflict_xid, we must use this as our horizon.
+ */
+ if (TransactionIdFollows(latest_xid_removed, conflict_xid))
+ {
+ Assert(do_prune);
+ conflict_xid = latest_xid_removed;
+ }
+
+ return conflict_xid;
+}
+
/*
* Helper to fix visibility-related corruption on a heap page and its
* corresponding VM page. An all-visible page cannot have dead items nor can
@@ -847,7 +918,7 @@ heap_fix_vm_corruption(PruneState *prstate, OffsetNumber offnum)
PageClearAllVisible(prstate->page);
MarkBufferDirtyHint(prstate->buffer, true);
}
- else if (prstate->vmbits & VISIBILITYMAP_VALID_BITS)
+ else if (prstate->old_vmbits & VISIBILITYMAP_VALID_BITS)
{
/*
* As of PostgreSQL 9.2, the visibility map bit should never be set if
@@ -865,7 +936,43 @@ heap_fix_vm_corruption(PruneState *prstate, OffsetNumber offnum)
visibilitymap_clear(prstate->relation, prstate->block, prstate->vmbuffer,
VISIBILITYMAP_VALID_BITS);
- prstate->vmbits = 0;
+ prstate->old_vmbits = 0;
+}
+
+/*
+ * Decide whether to set the visibility map bits (all-visible and all-frozen)
+ * for heap_blk using information from the PruneState and VM.
+ *
+ * This function does not actually set the VM bits or page-level visibility
+ * hint, PD_ALL_VISIBLE.
+ *
+ * Returns true if one or both VM bits should be set and false otherwise.
+ */
+static bool
+heap_page_will_set_vm(PruneState *prstate, PruneReason reason)
+{
+ /*
+ * Though on-access pruning maintains prstate->set_all_visible, we don't
+ * consider setting the VM.
+ */
+ if (reason == PRUNE_ON_ACCESS)
+ return false;
+
+ if (!prstate->set_all_visible)
+ return false;
+
+ prstate->new_vmbits = VISIBILITYMAP_ALL_VISIBLE;
+
+ if (prstate->set_all_frozen)
+ prstate->new_vmbits |= VISIBILITYMAP_ALL_FROZEN;
+
+ if (prstate->new_vmbits == prstate->old_vmbits)
+ {
+ prstate->new_vmbits = 0;
+ return false;
+ }
+
+ return true;
}
/*
@@ -894,15 +1001,13 @@ heap_page_bypass_prune_freeze(PruneState *prstate, PruneFreezeResult *presult)
OffsetNumber maxoff = PageGetMaxOffsetNumber(prstate->page);
Page page = prstate->page;
- Assert(prstate->vmbits & VISIBILITYMAP_ALL_FROZEN ||
- (prstate->vmbits & VISIBILITYMAP_ALL_VISIBLE &&
+ Assert(prstate->old_vmbits & VISIBILITYMAP_ALL_FROZEN ||
+ (prstate->old_vmbits & VISIBILITYMAP_ALL_VISIBLE &&
!prstate->attempt_freeze));
/* We'll fill in presult for the caller */
memset(presult, 0, sizeof(PruneFreezeResult));
- presult->vmbits = prstate->vmbits;
-
/* Clear any stale prune hint */
if (TransactionIdIsValid(PageGetPruneXid(page)))
{
@@ -932,7 +1037,8 @@ heap_page_bypass_prune_freeze(PruneState *prstate, PruneFreezeResult *presult)
/*
* Prune and repair fragmentation and potentially freeze tuples on the
- * specified page.
+ * specified page. If the page's visibility status has changed, update it in
+ * the VM.
*
* Caller must have pin and buffer cleanup lock on the page. Note that we
* don't update the FSM information for page on caller's behalf. Caller might
@@ -947,12 +1053,10 @@ heap_page_bypass_prune_freeze(PruneState *prstate, PruneFreezeResult *presult)
* tuples if it's required in order to advance relfrozenxid / relminmxid, or
* if it's considered advantageous for overall system performance to do so
* now. The 'params.cutoffs', 'presult', 'new_relfrozen_xid' and
- * 'new_relmin_mxid' arguments are required when freezing. When
- * HEAP_PAGE_PRUNE_FREEZE option is passed, we also set
- * presult->set_all_visible and presult->set_all_frozen after determining
- * whether or not to opportunistically freeze, to indicate if the VM bits can
- * be set. 'all-frozen' is always set to false when the HEAP_PAGE_PRUNE_FREEZE
- * option is not passed.
+ * 'new_relmin_mxid' arguments are required when freezing.
+ *
+ * A vmbuffer corresponding to the heap page is also is passed and if the page
+ * is found to be all-visible/all-frozen, we will set it in the VM.
*
* presult contains output parameters needed by callers, such as the number of
* tuples removed and the offsets of dead items on the page after pruning.
@@ -980,15 +1084,17 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
bool do_freeze;
bool do_prune;
bool do_hint_prune;
+ bool do_set_vm;
bool did_tuple_hint_fpi;
int64 fpi_before = pgWalUsage.wal_fpi;
+ TransactionId conflict_xid;
/* Initialize prstate */
prune_freeze_setup(params,
new_relfrozen_xid, new_relmin_mxid,
presult, &prstate);
- if ((prstate.vmbits & VISIBILITYMAP_VALID_BITS) &&
+ if ((prstate.old_vmbits & VISIBILITYMAP_VALID_BITS) &&
!PageIsAllVisible(prstate.page))
heap_fix_vm_corruption(&prstate, InvalidOffsetNumber);
@@ -997,8 +1103,8 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
* is not being attempted, we can exit early. Do this after fixing any
* discrepancy between the page-level visibility hint and the VM.
*/
- if (prstate.vmbits & VISIBILITYMAP_ALL_FROZEN ||
- (prstate.vmbits & VISIBILITYMAP_ALL_VISIBLE && !prstate.attempt_freeze))
+ if (prstate.old_vmbits & VISIBILITYMAP_ALL_FROZEN ||
+ (prstate.old_vmbits & VISIBILITYMAP_ALL_VISIBLE && !prstate.attempt_freeze))
{
heap_page_bypass_prune_freeze(&prstate, presult);
return;
@@ -1068,6 +1174,25 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
prstate.set_all_visible = prstate.set_all_frozen = false;
Assert(!prstate.set_all_frozen || prstate.set_all_visible);
+ Assert(!prstate.set_all_visible || (prstate.lpdead_items == 0));
+
+ do_set_vm = heap_page_will_set_vm(&prstate, params->reason);
+
+ /*
+ * new_vmbits should be 0 regardless of whether or not the page is
+ * all-visible if we do not intend to set the VM.
+ */
+ Assert(do_set_vm || prstate.new_vmbits == 0);
+
+ conflict_xid = get_conflict_xid(do_prune, do_freeze, do_set_vm,
+ prstate.old_vmbits, prstate.new_vmbits,
+ prstate.latest_xid_removed,
+ prstate.pagefrz.FreezePageConflictXid,
+ prstate.newest_live_xid);
+
+ /* Lock vmbuffer before entering a critical section */
+ if (do_set_vm)
+ LockBuffer(prstate.vmbuffer, BUFFER_LOCK_EXCLUSIVE);
/* Any error while applying the changes is critical */
START_CRIT_SECTION();
@@ -1089,14 +1214,17 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
/*
* If that's all we had to do to the page, this is a non-WAL-logged
- * hint. If we are going to freeze or prune the page, we will mark
- * the buffer dirty below.
+ * hint. If we are going to freeze or prune the page or set
+ * PD_ALL_VISIBLE, we will mark the buffer dirty below.
+ *
+ * Setting PD_ALL_VISIBLE is fully WAL-logged because it is forbidden
+ * for the VM to be set and PD_ALL_VISIBLE to be clear.
*/
- if (!do_freeze && !do_prune)
+ if (!do_freeze && !do_prune && !do_set_vm)
MarkBufferDirtyHint(prstate.buffer, true);
}
- if (do_prune || do_freeze)
+ if (do_prune || do_freeze || do_set_vm)
{
/* Apply the planned item changes and repair page fragmentation. */
if (do_prune)
@@ -1110,6 +1238,27 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
if (do_freeze)
heap_freeze_prepared_tuples(prstate.buffer, prstate.frozen, prstate.nfrozen);
+ /* Set the visibility map and page visibility hint */
+ if (do_set_vm)
+ {
+ /*
+ * While it is valid for PD_ALL_VISIBLE to be set when the
+ * corresponding VM bit is clear, we strongly prefer to keep them
+ * in sync.
+ *
+ * The heap buffer must be marked dirty before adding it to the
+ * WAL chain when setting the VM. We don't worry about
+ * unnecessarily dirtying the heap buffer if PD_ALL_VISIBLE is
+ * already set, though. It is extremely rare to have a clean heap
+ * buffer with PD_ALL_VISIBLE already set and the VM bits clear,
+ * so there is no point in optimizing it.
+ */
+ PageSetAllVisible(prstate.page);
+ PageClearPrunable(prstate.page);
+ visibilitymap_set_vmbits(prstate.block, prstate.vmbuffer, prstate.new_vmbits,
+ prstate.relation->rd_locator);
+ }
+
MarkBufferDirty(prstate.buffer);
/*
@@ -1117,29 +1266,12 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
*/
if (RelationNeedsWAL(prstate.relation))
{
- /*
- * The snapshotConflictHorizon for the whole record should be the
- * most conservative of all the horizons calculated for any of the
- * possible modifications. If this record will prune tuples, any
- * queries on the standby older than the youngest xid of the most
- * recently removed tuple this record will prune will conflict. If
- * this record will freeze tuples, any queries on the standby with
- * xids older than the youngest tuple this record will freeze will
- * conflict.
- */
- TransactionId conflict_xid;
-
- if (TransactionIdFollows(prstate.pagefrz.FreezePageConflictXid,
- prstate.latest_xid_removed))
- conflict_xid = prstate.pagefrz.FreezePageConflictXid;
- else
- conflict_xid = prstate.latest_xid_removed;
-
log_heap_prune_and_freeze(prstate.relation, prstate.buffer,
- InvalidBuffer, /* vmbuffer */
- 0, /* vmflags */
+ do_set_vm ? prstate.vmbuffer : InvalidBuffer,
+ do_set_vm ? prstate.new_vmbits : 0,
conflict_xid,
- true, params->reason,
+ true, /* cleanup lock */
+ params->reason,
prstate.frozen, prstate.nfrozen,
prstate.redirected, prstate.nredirected,
prstate.nowdead, prstate.ndead,
@@ -1149,33 +1281,70 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
END_CRIT_SECTION();
+ if (do_set_vm)
+ LockBuffer(prstate.vmbuffer, BUFFER_LOCK_UNLOCK);
+
+ /*
+ * During its second pass over the heap, VACUUM calls
+ * heap_page_would_be_all_visible() to determine whether a page is
+ * all-visible and all-frozen. The logic here is similar. After completing
+ * pruning and freezing, use an assertion to verify that our results
+ * remain consistent with heap_page_would_be_all_visible().
+ */
+#ifdef USE_ASSERT_CHECKING
+ if (prstate.set_all_visible)
+ {
+ TransactionId debug_cutoff;
+ bool debug_all_frozen;
+
+ Assert(prstate.lpdead_items == 0);
+
+ Assert(heap_page_is_all_visible(prstate.relation, prstate.buffer,
+ prstate.vistest,
+ &debug_all_frozen,
+ &debug_cutoff, off_loc));
+
+ /*
+ * It's possible the page is composed entirely of frozen tuples but is
+ * not set all-frozen in the VM and did not pass
+ * HEAP_PAGE_PRUNE_FREEZE. In this case, it's possible
+ * heap_page_is_all_visible() finds the page completely frozen, even
+ * though prstate.all_frozen is false.
+ */
+ Assert(!prstate.set_all_frozen || debug_all_frozen);
+ }
+#endif
+
/* Copy information back for caller */
presult->ndeleted = prstate.ndeleted;
presult->nnewlpdead = prstate.ndead;
presult->nfrozen = prstate.nfrozen;
presult->live_tuples = prstate.live_tuples;
presult->recently_dead_tuples = prstate.recently_dead_tuples;
- presult->set_all_visible = prstate.set_all_visible;
- presult->set_all_frozen = prstate.set_all_frozen;
presult->hastup = prstate.hastup;
- presult->vmbits = prstate.vmbits;
-
- /*
- * For callers planning to update the visibility map, the conflict horizon
- * for that record must be the newest xmin on the page. However, if the
- * page is completely frozen, there can be no conflict and the
- * vm_conflict_horizon should remain InvalidTransactionId. This includes
- * the case that we just froze all the tuples; the prune-freeze record
- * included the conflict XID already so the caller doesn't need it.
- */
- if (presult->set_all_frozen)
- presult->vm_conflict_horizon = InvalidTransactionId;
- else
- presult->vm_conflict_horizon = prstate.newest_live_xid;
presult->lpdead_items = prstate.lpdead_items;
/* the presult->deadoffsets array was already filled in */
+ if (do_set_vm)
+ {
+ if ((prstate.old_vmbits & VISIBILITYMAP_ALL_VISIBLE) == 0)
+ {
+ presult->new_all_visible_pages = 1;
+ if (prstate.set_all_frozen)
+ presult->new_all_visible_frozen_pages = 1;
+ }
+ else if ((prstate.old_vmbits & VISIBILITYMAP_ALL_FROZEN) == 0 &&
+ prstate.set_all_frozen)
+ presult->new_all_frozen_pages = 1;
+ }
+ else
+ {
+ presult->new_all_visible_pages = 0;
+ presult->new_all_frozen_pages = 0;
+ presult->new_all_visible_frozen_pages = 0;
+ }
+
if (prstate.attempt_freeze)
{
if (presult->nfrozen > 0)
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 23402e7e26c..6b5210d6393 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -459,13 +459,6 @@ static void dead_items_add(LVRelState *vacrel, BlockNumber blkno, OffsetNumber *
static void dead_items_reset(LVRelState *vacrel);
static void dead_items_cleanup(LVRelState *vacrel);
-#ifdef USE_ASSERT_CHECKING
-static bool heap_page_is_all_visible(Relation rel, Buffer buf,
- GlobalVisState *vistest,
- bool *all_frozen,
- TransactionId *newest_live_xid,
- OffsetNumber *logging_offnum);
-#endif
static bool heap_page_would_be_all_visible(Relation rel, Buffer buf,
GlobalVisState *vistest,
OffsetNumber *deadoffsets,
@@ -1996,8 +1989,6 @@ lazy_scan_prune(LVRelState *vacrel,
.vistest = vacrel->vistest,
.cutoffs = &vacrel->cutoffs,
};
- uint8 old_vmbits = 0;
- uint8 new_vmbits = 0;
Assert(BufferGetBlockNumber(buf) == blkno);
@@ -2038,29 +2029,6 @@ lazy_scan_prune(LVRelState *vacrel,
vacrel->new_frozen_tuple_pages++;
}
- /*
- * VACUUM will call heap_page_is_all_visible() during the second pass over
- * the heap to determine all_visible and all_frozen for the page -- this
- * is a specialized version of the logic from this function. Now that
- * we've finished pruning and freezing, make sure that we're in total
- * agreement with heap_page_is_all_visible() using an assertion.
- */
-#ifdef USE_ASSERT_CHECKING
- if (presult.set_all_visible)
- {
- TransactionId debug_cutoff;
- bool debug_all_frozen;
-
- Assert(presult.lpdead_items == 0);
-
- Assert(heap_page_is_all_visible(vacrel->rel, buf,
- vacrel->vistest, &debug_all_frozen,
- &debug_cutoff, &vacrel->offnum));
-
- Assert(presult.set_all_frozen == debug_all_frozen);
- }
-#endif
-
/*
* Now save details of the LP_DEAD items from the page in vacrel
*/
@@ -2081,6 +2049,14 @@ lazy_scan_prune(LVRelState *vacrel,
}
/* Finally, add page-local counts to whole-VACUUM counts */
+ vacrel->new_all_visible_pages += presult.new_all_visible_pages;
+ vacrel->new_all_visible_all_frozen_pages += presult.new_all_visible_frozen_pages;
+ vacrel->new_all_frozen_pages += presult.new_all_frozen_pages;
+
+ /* Capture if the page was newly set frozen */
+ *vm_page_frozen = presult.new_all_visible_frozen_pages > 0 ||
+ presult.new_all_frozen_pages > 0;
+
vacrel->tuples_deleted += presult.ndeleted;
vacrel->tuples_frozen += presult.nfrozen;
vacrel->lpdead_items += presult.lpdead_items;
@@ -2094,71 +2070,6 @@ lazy_scan_prune(LVRelState *vacrel,
/* Did we find LP_DEAD items? */
*has_lpdead_items = (presult.lpdead_items > 0);
- Assert(!presult.set_all_visible || !(*has_lpdead_items));
- Assert(!presult.set_all_frozen || presult.set_all_visible);
-
- if (!presult.set_all_visible)
- return presult.ndeleted;
-
- /* Set the visibility map and page visibility hint */
- old_vmbits = presult.vmbits;
- new_vmbits = VISIBILITYMAP_ALL_VISIBLE;
- if (presult.set_all_frozen)
- new_vmbits |= VISIBILITYMAP_ALL_FROZEN;
-
- /* Nothing to do */
- if (old_vmbits == new_vmbits)
- return presult.ndeleted;
-
- /*
- * It should never be the case that the visibility map page is set while
- * the page-level bit is clear (and if so, we cleared it above), but the
- * reverse is allowed (if checksums are not enabled). Regardless, set both
- * bits so that we get back in sync.
- *
- * The heap buffer must be marked dirty before adding it to the WAL chain
- * when setting the VM. We don't worry about unnecessarily dirtying the
- * heap buffer if PD_ALL_VISIBLE is already set, though. It is extremely
- * rare to have a clean heap buffer with PD_ALL_VISIBLE already set and
- * the VM bits clear, so there is no point in optimizing it.
- */
- PageSetAllVisible(page);
- PageClearPrunable(page);
- MarkBufferDirty(buf);
-
- /*
- * If the page is being set all-frozen, we pass InvalidTransactionId as
- * the cutoff_xid, since a snapshot conflict horizon sufficient to make
- * everything safe for REDO was logged when the page's tuples were frozen.
- */
- Assert(!presult.set_all_frozen ||
- !TransactionIdIsValid(presult.vm_conflict_horizon));
-
- visibilitymap_set(vacrel->rel, blkno, buf,
- InvalidXLogRecPtr,
- vmbuffer, presult.vm_conflict_horizon,
- new_vmbits);
-
- /*
- * If the page wasn't already set all-visible and/or all-frozen in the VM,
- * count it as newly set for logging.
- */
- if ((old_vmbits & VISIBILITYMAP_ALL_VISIBLE) == 0)
- {
- vacrel->new_all_visible_pages++;
- if (presult.set_all_frozen)
- {
- vacrel->new_all_visible_all_frozen_pages++;
- *vm_page_frozen = true;
- }
- }
- else if ((old_vmbits & VISIBILITYMAP_ALL_FROZEN) == 0 &&
- presult.set_all_frozen)
- {
- vacrel->new_all_frozen_pages++;
- *vm_page_frozen = true;
- }
-
return presult.ndeleted;
}
@@ -3572,7 +3483,7 @@ dead_items_cleanup(LVRelState *vacrel)
* that expect no LP_DEAD on the page. Currently assert-only, but there is no
* reason not to use it outside of asserts.
*/
-static bool
+bool
heap_page_is_all_visible(Relation rel, Buffer buf,
GlobalVisState *vistest,
bool *all_frozen,
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 849ed82bcf2..7ef4cbbfb1e 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -260,7 +260,8 @@ typedef struct PruneFreezeParams
/*
* Callers should provide a pinned vmbuffer corresponding to the heap
- * block in buffer. We will check for and repair any corruption in the VM.
+ * block in buffer. We will check for and repair any corruption in the VM
+ * and set the VM after pruning if the page is all-visible/all-frozen.
*/
Buffer vmbuffer;
@@ -276,8 +277,7 @@ typedef struct PruneFreezeParams
* HEAP_PAGE_PRUNE_MARK_UNUSED_NOW indicates that dead items can be set
* LP_UNUSED during pruning.
*
- * HEAP_PAGE_PRUNE_FREEZE indicates that we will also freeze tuples, and
- * will return 'all_visible', 'all_frozen' flags to the caller.
+ * HEAP_PAGE_PRUNE_FREEZE indicates that we will also freeze tuples.
*/
int options;
@@ -311,26 +311,12 @@ typedef struct PruneFreezeResult
int recently_dead_tuples;
/*
- * set_all_visible and set_all_frozen indicate if the all-visible and
- * all-frozen bits in the visibility map should be set for this page after
- * pruning.
- *
- * vm_conflict_horizon is the newest xmin of live tuples on the page. The
- * caller can use it as the conflict horizon when setting the VM bits. It
- * is only valid if we froze some tuples (nfrozen > 0), and set_all_frozen
- * is true.
- *
- * These are only set if the HEAP_PAGE_PRUNE_FREEZE option is set.
- */
- bool set_all_visible;
- bool set_all_frozen;
- TransactionId vm_conflict_horizon;
-
- /*
- * vmbits is the value of the vmbuffer's vmbits at the beginning of
- * pruning. It is cleared if VM corruption is found and corrected.
+ * Whether or not the page was newly set all-visible and all-frozen during
+ * phase I of vacuuming.
*/
- uint8 vmbits;
+ BlockNumber new_all_visible_pages;
+ BlockNumber new_all_visible_frozen_pages;
+ BlockNumber new_all_frozen_pages;
/*
* Whether or not the page makes rel truncation unsafe. This is set to
@@ -467,7 +453,13 @@ extern void log_heap_prune_and_freeze(Relation relation, Buffer buffer,
/* in heap/vacuumlazy.c */
extern void heap_vacuum_rel(Relation rel,
const VacuumParams params, BufferAccessStrategy bstrategy);
-
+#ifdef USE_ASSERT_CHECKING
+extern bool heap_page_is_all_visible(Relation rel, Buffer buf,
+ GlobalVisState *vistest,
+ bool *all_frozen,
+ TransactionId *visibility_cutoff_xid,
+ OffsetNumber *logging_offnum);
+#endif
/* in heap/heapam_visibility.c */
extern bool HeapTupleSatisfiesVisibility(HeapTuple htup, Snapshot snapshot,
Buffer buffer);
--
2.43.0