v18-0012-Split-heap_page_prune_and_freeze-into-helpers.patch
text/x-patch
Filename: v18-0012-Split-heap_page_prune_and_freeze-into-helpers.patch
Type: text/x-patch
Part: 11
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 v18-0012
Subject: Split heap_page_prune_and_freeze into helpers
| File | + | − |
|---|---|---|
| src/backend/access/heap/pruneheap.c | 170 | 146 |
From 163e09cb81eeb1af31cd9b3a648896845587ce3a Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 8 Oct 2025 18:45:45 -0400
Subject: [PATCH v18 12/12] Split heap_page_prune_and_freeze into helpers
---
src/backend/access/heap/pruneheap.c | 316 +++++++++++++++-------------
1 file changed, 170 insertions(+), 146 deletions(-)
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 6e863ffd85e..d21a66f6a75 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -590,82 +590,20 @@ heap_page_will_set_vis(Relation relation,
return do_set_vm;
}
-/*
- * Prune and repair fragmentation and potentially freeze tuples on the
- * 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
- * also need to account for a reduction in the length of the line pointer
- * array following array truncation by us.
- *
- * params contains the input parameters used to control freezing and pruning
- * behavior. See the definition of PruneFreezeParams for more on what each
- * parameter does.
- *
- * If the HEAP_PRUNE_FREEZE option is set in params, we will freeze 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.
- *
- * If HEAP_PAGE_PRUNE_UPDATE_VIS is set in params and the visibility status of
- * the page has changed, we will update the VM at the same time as pruning and
- * freezing the heap page. We will also update presult->old_vmbits and
- * presult->new_vmbits with the state of the VM before and after updating it
- * for the caller to use in bookkeeping.
- *
- * 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.
- * heap_page_prune_and_freeze() is responsible for initializing it. Required
- * by all callers.
- *
- * off_loc is the offset location required by the caller to use in error
- * callback.
- *
- * new_relfrozen_xid and new_relmin_mxid must provided by the caller if the
- * HEAP_PRUNE_FREEZE option is set in params. On entry, they contain the
- * oldest XID and multi-XID seen on the relation so far. They will be updated
- * with oldest values present on the page after pruning. After processing the
- * whole relation, VACUUM can use these values as the new
- * relfrozenxid/relminmxid for the relation.
- */
-void
-heap_page_prune_and_freeze(PruneFreezeParams *params,
- PruneFreezeResult *presult,
- OffsetNumber *off_loc,
- TransactionId *new_relfrozen_xid,
- MultiXactId *new_relmin_mxid)
+static void
+prune_freeze_setup(PruneFreezeParams *params, PruneState *prstate,
+ TransactionId *new_relfrozen_xid,
+ MultiXactId *new_relmin_mxid,
+ PruneFreezeResult *presult)
{
- Buffer buffer = params->buffer;
- Buffer vmbuffer = params->vmbuffer;
- Page page = BufferGetPage(buffer);
- BlockNumber blockno = BufferGetBlockNumber(buffer);
- OffsetNumber offnum,
- maxoff;
- PruneState prstate;
- HeapTupleData tup;
- bool do_freeze;
- bool do_prune;
- bool do_hint_prune;
- bool do_set_vm;
- bool do_set_pd_vis;
- bool did_tuple_hint_fpi;
- int64 fpi_before = pgWalUsage.wal_fpi;
- TransactionId frz_conflict_horizon = InvalidTransactionId;
- TransactionId conflict_xid = InvalidTransactionId;
- uint8 new_vmbits = 0;
- uint8 old_vmbits = 0;
-
/* Copy parameters to prstate */
- prstate.vistest = params->vistest;
- prstate.mark_unused_now =
+ prstate->vistest = params->vistest;
+ prstate->mark_unused_now =
(params->options & HEAP_PAGE_PRUNE_MARK_UNUSED_NOW) != 0;
- prstate.attempt_freeze = (params->options & HEAP_PAGE_PRUNE_FREEZE) != 0;
- prstate.attempt_update_vm =
+ prstate->attempt_freeze = (params->options & HEAP_PAGE_PRUNE_FREEZE) != 0;
+ prstate->attempt_update_vm =
(params->options & HEAP_PAGE_PRUNE_UPDATE_VIS) != 0;
- prstate.cutoffs = params->cutoffs;
+ prstate->cutoffs = params->cutoffs;
/*
* Our strategy is to scan the page and make lists of items to change,
@@ -678,37 +616,37 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
* prunable, we will save the lowest relevant XID in new_prune_xid. Also
* initialize the rest of our working state.
*/
- prstate.new_prune_xid = InvalidTransactionId;
- prstate.latest_xid_removed = InvalidTransactionId;
- prstate.nredirected = prstate.ndead = prstate.nunused = prstate.nfrozen = 0;
- prstate.nroot_items = 0;
- prstate.nheaponly_items = 0;
+ prstate->new_prune_xid = InvalidTransactionId;
+ prstate->latest_xid_removed = InvalidTransactionId;
+ prstate->nredirected = prstate->ndead = prstate->nunused = prstate->nfrozen = 0;
+ prstate->nroot_items = 0;
+ prstate->nheaponly_items = 0;
/* initialize page freezing working state */
- prstate.pagefrz.freeze_required = false;
- if (prstate.attempt_freeze)
+ prstate->pagefrz.freeze_required = false;
+ if (prstate->attempt_freeze)
{
Assert(new_relfrozen_xid && new_relmin_mxid);
- prstate.pagefrz.FreezePageRelfrozenXid = *new_relfrozen_xid;
- prstate.pagefrz.NoFreezePageRelfrozenXid = *new_relfrozen_xid;
- prstate.pagefrz.FreezePageRelminMxid = *new_relmin_mxid;
- prstate.pagefrz.NoFreezePageRelminMxid = *new_relmin_mxid;
+ prstate->pagefrz.FreezePageRelfrozenXid = *new_relfrozen_xid;
+ prstate->pagefrz.NoFreezePageRelfrozenXid = *new_relfrozen_xid;
+ prstate->pagefrz.FreezePageRelminMxid = *new_relmin_mxid;
+ prstate->pagefrz.NoFreezePageRelminMxid = *new_relmin_mxid;
}
else
{
Assert(new_relfrozen_xid == NULL && new_relmin_mxid == NULL);
- prstate.pagefrz.FreezePageRelminMxid = InvalidMultiXactId;
- prstate.pagefrz.NoFreezePageRelminMxid = InvalidMultiXactId;
- prstate.pagefrz.FreezePageRelfrozenXid = InvalidTransactionId;
- prstate.pagefrz.NoFreezePageRelfrozenXid = InvalidTransactionId;
+ prstate->pagefrz.FreezePageRelminMxid = InvalidMultiXactId;
+ prstate->pagefrz.NoFreezePageRelminMxid = InvalidMultiXactId;
+ prstate->pagefrz.FreezePageRelfrozenXid = InvalidTransactionId;
+ prstate->pagefrz.NoFreezePageRelfrozenXid = InvalidTransactionId;
}
- prstate.ndeleted = 0;
- prstate.live_tuples = 0;
- prstate.recently_dead_tuples = 0;
- prstate.hastup = false;
- prstate.lpdead_items = 0;
- prstate.deadoffsets = presult->deadoffsets;
+ prstate->ndeleted = 0;
+ prstate->live_tuples = 0;
+ prstate->recently_dead_tuples = 0;
+ prstate->hastup = false;
+ prstate->lpdead_items = 0;
+ prstate->deadoffsets = presult->deadoffsets;
/*
* Track whether the page could be marked all-visible and/or all-frozen.
@@ -736,20 +674,20 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
* bookkeeping. In this case, initializing all_visible to false allows
* heap_prune_record_unchanged_lp_normal() to bypass unnecessary work.
*/
- if (prstate.attempt_freeze)
+ if (prstate->attempt_freeze)
{
- prstate.all_visible = true;
- prstate.all_frozen = true;
+ prstate->all_visible = true;
+ prstate->all_frozen = true;
}
- else if (prstate.attempt_update_vm)
+ else if (prstate->attempt_update_vm)
{
- prstate.all_visible = true;
- prstate.all_frozen = false;
+ prstate->all_visible = true;
+ prstate->all_frozen = false;
}
else
{
- prstate.all_visible = false;
- prstate.all_frozen = false;
+ prstate->all_visible = false;
+ prstate->all_frozen = false;
}
/*
@@ -761,10 +699,14 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
* used to calculate the snapshot conflict horizon when updating the VM
* and/or freezing all the tuples on the page.
*/
- prstate.visibility_cutoff_xid = InvalidTransactionId;
+ prstate->visibility_cutoff_xid = InvalidTransactionId;
+}
- maxoff = PageGetMaxOffsetNumber(page);
- tup.t_tableOid = RelationGetRelid(params->relation);
+static void
+prune_freeze_plan(PruneState *prstate, BlockNumber blockno, Buffer buffer, Page page,
+ OffsetNumber maxoff, OffsetNumber *off_loc, HeapTuple tup)
+{
+ OffsetNumber offnum;
/*
* Determine HTSV for all tuples, and queue them up for processing as HOT
@@ -799,13 +741,13 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
*/
*off_loc = offnum;
- prstate.processed[offnum] = false;
- prstate.htsv[offnum] = -1;
+ prstate->processed[offnum] = false;
+ prstate->htsv[offnum] = -1;
/* Nothing to do if slot doesn't contain a tuple */
if (!ItemIdIsUsed(itemid))
{
- heap_prune_record_unchanged_lp_unused(page, &prstate, offnum);
+ heap_prune_record_unchanged_lp_unused(page, prstate, offnum);
continue;
}
@@ -815,17 +757,17 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
* If the caller set mark_unused_now true, we can set dead line
* pointers LP_UNUSED now.
*/
- if (unlikely(prstate.mark_unused_now))
- heap_prune_record_unused(&prstate, offnum, false);
+ if (unlikely(prstate->mark_unused_now))
+ heap_prune_record_unused(prstate, offnum, false);
else
- heap_prune_record_unchanged_lp_dead(page, &prstate, offnum);
+ heap_prune_record_unchanged_lp_dead(page, prstate, offnum);
continue;
}
if (ItemIdIsRedirected(itemid))
{
/* This is the start of a HOT chain */
- prstate.root_items[prstate.nroot_items++] = offnum;
+ prstate->root_items[prstate->nroot_items++] = offnum;
continue;
}
@@ -835,25 +777,19 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
* Get the tuple's visibility status and queue it up for processing.
*/
htup = (HeapTupleHeader) PageGetItem(page, itemid);
- tup.t_data = htup;
- tup.t_len = ItemIdGetLength(itemid);
- ItemPointerSet(&tup.t_self, blockno, offnum);
+ tup->t_data = htup;
+ tup->t_len = ItemIdGetLength(itemid);
+ ItemPointerSet(&tup->t_self, blockno, offnum);
- prstate.htsv[offnum] = heap_prune_satisfies_vacuum(&prstate, &tup,
- buffer);
+ prstate->htsv[offnum] = heap_prune_satisfies_vacuum(prstate, tup,
+ buffer);
if (!HeapTupleHeaderIsHeapOnly(htup))
- prstate.root_items[prstate.nroot_items++] = offnum;
+ prstate->root_items[prstate->nroot_items++] = offnum;
else
- prstate.heaponly_items[prstate.nheaponly_items++] = offnum;
+ prstate->heaponly_items[prstate->nheaponly_items++] = offnum;
}
- /*
- * If checksums are enabled, heap_prune_satisfies_vacuum() may have caused
- * an FPI to be emitted.
- */
- did_tuple_hint_fpi = fpi_before != pgWalUsage.wal_fpi;
-
/*
* Process HOT chains.
*
@@ -865,30 +801,30 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
* the page instead of using the root_items array, also did it in
* ascending offset number order.)
*/
- for (int i = prstate.nroot_items - 1; i >= 0; i--)
+ for (int i = prstate->nroot_items - 1; i >= 0; i--)
{
- offnum = prstate.root_items[i];
+ offnum = prstate->root_items[i];
/* Ignore items already processed as part of an earlier chain */
- if (prstate.processed[offnum])
+ if (prstate->processed[offnum])
continue;
/* see preceding loop */
*off_loc = offnum;
/* Process this item or chain of items */
- heap_prune_chain(page, blockno, maxoff, offnum, &prstate);
+ heap_prune_chain(page, blockno, maxoff, offnum, prstate);
}
/*
* Process any heap-only tuples that were not already processed as part of
* a HOT chain.
*/
- for (int i = prstate.nheaponly_items - 1; i >= 0; i--)
+ for (int i = prstate->nheaponly_items - 1; i >= 0; i--)
{
- offnum = prstate.heaponly_items[i];
+ offnum = prstate->heaponly_items[i];
- if (prstate.processed[offnum])
+ if (prstate->processed[offnum])
continue;
/* see preceding loop */
@@ -907,7 +843,7 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
* return true for an XMIN_INVALID tuple, so this code will work even
* when there were sequential updates within the aborted transaction.)
*/
- if (prstate.htsv[offnum] == HEAPTUPLE_DEAD)
+ if (prstate->htsv[offnum] == HEAPTUPLE_DEAD)
{
ItemId itemid = PageGetItemId(page, offnum);
HeapTupleHeader htup = (HeapTupleHeader) PageGetItem(page, itemid);
@@ -915,8 +851,8 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
if (likely(!HeapTupleHeaderIsHotUpdated(htup)))
{
HeapTupleHeaderAdvanceConflictHorizon(htup,
- &prstate.latest_xid_removed);
- heap_prune_record_unused(&prstate, offnum, true);
+ &prstate->latest_xid_removed);
+ heap_prune_record_unused(prstate, offnum, true);
}
else
{
@@ -933,7 +869,7 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
}
}
else
- heap_prune_record_unchanged_lp_normal(page, &prstate, offnum);
+ heap_prune_record_unchanged_lp_normal(page, prstate, offnum);
}
/* We should now have processed every tuple exactly once */
@@ -944,12 +880,110 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
{
*off_loc = offnum;
- Assert(prstate.processed[offnum]);
+ Assert(prstate->processed[offnum]);
}
#endif
+ /*
+ * After processing all the live tuples on the page, if the newest xmin
+ * amongst them is not visible to everyone, the page cannot be
+ * all-visible.
+ */
+ if (prstate->all_visible &&
+ TransactionIdIsNormal(prstate->visibility_cutoff_xid) &&
+ !GlobalVisXidVisibleToAll(prstate->vistest, prstate->visibility_cutoff_xid))
+ prstate->all_visible = prstate->all_frozen = false;
+
/* Clear the offset information once we have processed the given page. */
*off_loc = InvalidOffsetNumber;
+}
+
+/*
+ * Prune and repair fragmentation and potentially freeze tuples on the
+ * 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
+ * also need to account for a reduction in the length of the line pointer
+ * array following array truncation by us.
+ *
+ * params contains the input parameters used to control freezing and pruning
+ * behavior. See the definition of PruneFreezeParams for more on what each
+ * parameter does.
+ *
+ * If the HEAP_PRUNE_FREEZE option is set in params, we will freeze 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.
+ *
+ * If HEAP_PAGE_PRUNE_UPDATE_VIS is set in params and the visibility status of
+ * the page has changed, we will update the VM at the same time as pruning and
+ * freezing the heap page. We will also update presult->old_vmbits and
+ * presult->new_vmbits with the state of the VM before and after updating it
+ * for the caller to use in bookkeeping.
+ *
+ * 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.
+ * heap_page_prune_and_freeze() is responsible for initializing it. Required
+ * by all callers.
+ *
+ * off_loc is the offset location required by the caller to use in error
+ * callback.
+ *
+ * new_relfrozen_xid and new_relmin_mxid must provided by the caller if the
+ * HEAP_PRUNE_FREEZE option is set in params. On entry, they contain the
+ * oldest XID and multi-XID seen on the relation so far. They will be updated
+ * with oldest values present on the page after pruning. After processing the
+ * whole relation, VACUUM can use these values as the new
+ * relfrozenxid/relminmxid for the relation.
+ */
+void
+heap_page_prune_and_freeze(PruneFreezeParams *params,
+ PruneFreezeResult *presult,
+ OffsetNumber *off_loc,
+ TransactionId *new_relfrozen_xid,
+ MultiXactId *new_relmin_mxid)
+{
+ Buffer buffer = params->buffer;
+ Buffer vmbuffer = params->vmbuffer;
+ Page page = BufferGetPage(buffer);
+ BlockNumber blockno = BufferGetBlockNumber(buffer);
+ OffsetNumber maxoff;
+ PruneState prstate;
+ HeapTupleData tup;
+ bool do_freeze;
+ bool do_prune;
+ bool do_hint_prune;
+ bool do_set_vm;
+ bool do_set_pd_vis;
+ bool did_tuple_hint_fpi;
+ int64 fpi_before = pgWalUsage.wal_fpi;
+ TransactionId frz_conflict_horizon = InvalidTransactionId;
+ TransactionId conflict_xid = InvalidTransactionId;
+ uint8 new_vmbits = 0;
+ uint8 old_vmbits = 0;
+
+ maxoff = PageGetMaxOffsetNumber(page);
+ tup.t_tableOid = RelationGetRelid(params->relation);
+
+ /* Initialize needed state in prstate */
+ prune_freeze_setup(params, &prstate, new_relfrozen_xid, new_relmin_mxid, presult);
+
+ /*
+ * Examine all line pointers and tuple visibility information to determine
+ * which line pointers should change state and which tuples may be frozen.
+ * Prepare queue of state changes to later be executed in a critical
+ * section.
+ */
+ prune_freeze_plan(&prstate, blockno, buffer, page, maxoff, off_loc, &tup);
+
+ /*
+ * If checksums are enabled, heap_prune_satisfies_vacuum() may have caused
+ * an FPI to be emitted.
+ */
+ did_tuple_hint_fpi = fpi_before != pgWalUsage.wal_fpi;
do_prune = prstate.nredirected > 0 ||
prstate.ndead > 0 ||
@@ -963,16 +997,6 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
do_hint_prune = ((PageHeader) page)->pd_prune_xid != prstate.new_prune_xid ||
PageIsFull(page);
- /*
- * After processing all the live tuples on the page, if the newest xmin
- * amongst them is not visible to everyone, the page cannot be
- * all-visible.
- */
- if (prstate.all_visible &&
- TransactionIdIsNormal(prstate.visibility_cutoff_xid) &&
- !GlobalVisXidVisibleToAll(prstate.vistest, prstate.visibility_cutoff_xid))
- prstate.all_visible = prstate.all_frozen = false;
-
/*
* Decide if we want to go ahead with freezing according to the freeze
* plans we prepared, or not.
--
2.43.0