1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-09 06:21:09 +03:00

Add helper for freeze determination to heap_page_prune_and_freeze

After scanning the line pointers on a heap page during the first phase
of vacuum, we use the information collected to decide whether to use
the assembled freeze plans.

Move this decision logic into a helper function to improve readability.

While here, rename a PruneState member and disambiguate some local
variables in heap_page_prune_and_freeze().

Author: Melanie Plageman <melanieplageman@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Discussion: https://postgr.es/m/2wk7jo4m4qwh5sn33pfgerdjfujebbccsmmlownybddbh6nawl%40mdyyqpqzxjek
This commit is contained in:
Melanie Plageman
2025-10-14 15:07:40 -04:00
parent 74ac377d75
commit c8dd6542ba

View File

@@ -43,7 +43,7 @@ typedef struct
/* whether or not dead items can be set LP_UNUSED during pruning */ /* whether or not dead items can be set LP_UNUSED during pruning */
bool mark_unused_now; bool mark_unused_now;
/* whether to attempt freezing tuples */ /* whether to attempt freezing tuples */
bool freeze; bool attempt_freeze;
struct VacuumCutoffs *cutoffs; struct VacuumCutoffs *cutoffs;
/*------------------------------------------------------- /*-------------------------------------------------------
@@ -177,6 +177,10 @@ static void heap_prune_record_unchanged_lp_redirect(PruneState *prstate, OffsetN
static void page_verify_redirects(Page page); static void page_verify_redirects(Page page);
static bool heap_page_will_freeze(Relation relation, Buffer buffer,
bool did_tuple_hint_fpi, bool do_prune, bool do_hint_prune,
PruneState *prstate);
/* /*
* Optionally prune and repair fragmentation in the specified page. * Optionally prune and repair fragmentation in the specified page.
@@ -294,6 +298,117 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
} }
} }
/*
* Decide whether to proceed with freezing according to the freeze plans
* prepared for the given heap buffer. If freezing is chosen, this function
* performs several pre-freeze checks.
*
* The values of do_prune, do_hint_prune, and did_tuple_hint_fpi must be
* determined before calling this function.
*
* prstate is both an input and output parameter.
*
* Returns true if we should apply the freeze plans and freeze tuples on the
* page, and false otherwise.
*/
static bool
heap_page_will_freeze(Relation relation, Buffer buffer,
bool did_tuple_hint_fpi,
bool do_prune,
bool do_hint_prune,
PruneState *prstate)
{
bool do_freeze = false;
/*
* If the caller specified we should not attempt to freeze any tuples,
* validate that everything is in the right state and return.
*/
if (!prstate->attempt_freeze)
{
Assert(!prstate->all_frozen && prstate->nfrozen == 0);
Assert(prstate->lpdead_items == 0 || !prstate->all_visible);
return false;
}
if (prstate->pagefrz.freeze_required)
{
/*
* heap_prepare_freeze_tuple indicated that at least one XID/MXID from
* before FreezeLimit/MultiXactCutoff is present. Must freeze to
* advance relfrozenxid/relminmxid.
*/
do_freeze = true;
}
else
{
/*
* Opportunistically freeze the page if we are generating an FPI
* anyway and if doing so means that we can set the page all-frozen
* afterwards (might not happen until VACUUM's final heap pass).
*
* XXX: Previously, we knew if pruning emitted an FPI by checking
* pgWalUsage.wal_fpi before and after pruning. Once the freeze and
* prune records were combined, this heuristic couldn't be used
* anymore. The opportunistic freeze heuristic must be improved;
* however, for now, try to approximate the old logic.
*/
if (prstate->all_visible && prstate->all_frozen && prstate->nfrozen > 0)
{
/*
* Freezing would make the page all-frozen. Have already emitted
* an FPI or will do so anyway?
*/
if (RelationNeedsWAL(relation))
{
if (did_tuple_hint_fpi)
do_freeze = true;
else if (do_prune)
{
if (XLogCheckBufferNeedsBackup(buffer))
do_freeze = true;
}
else if (do_hint_prune)
{
if (XLogHintBitIsNeeded() && XLogCheckBufferNeedsBackup(buffer))
do_freeze = true;
}
}
}
}
if (do_freeze)
{
/*
* Validate the tuples we will be freezing before entering the
* critical section.
*/
heap_pre_freeze_checks(buffer, prstate->frozen, prstate->nfrozen);
}
else if (prstate->nfrozen > 0)
{
/*
* The page contained some tuples that were not already frozen, and we
* chose not to freeze them now. The page won't be all-frozen then.
*/
Assert(!prstate->pagefrz.freeze_required);
prstate->all_frozen = false;
prstate->nfrozen = 0; /* avoid miscounts in instrumentation */
}
else
{
/*
* We have no freeze plans to execute. The page might already be
* all-frozen (perhaps only following pruning), though. Such pages
* can be marked all-frozen in the VM by our caller, even though none
* of its tuples were newly frozen here.
*/
}
return do_freeze;
}
/* /*
* Prune and repair fragmentation and potentially freeze tuples on the * Prune and repair fragmentation and potentially freeze tuples on the
@@ -366,14 +481,14 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer,
HeapTupleData tup; HeapTupleData tup;
bool do_freeze; bool do_freeze;
bool do_prune; bool do_prune;
bool do_hint; bool do_hint_prune;
bool hint_bit_fpi; bool did_tuple_hint_fpi;
int64 fpi_before = pgWalUsage.wal_fpi; int64 fpi_before = pgWalUsage.wal_fpi;
/* Copy parameters to prstate */ /* Copy parameters to prstate */
prstate.vistest = vistest; prstate.vistest = vistest;
prstate.mark_unused_now = (options & HEAP_PAGE_PRUNE_MARK_UNUSED_NOW) != 0; prstate.mark_unused_now = (options & HEAP_PAGE_PRUNE_MARK_UNUSED_NOW) != 0;
prstate.freeze = (options & HEAP_PAGE_PRUNE_FREEZE) != 0; prstate.attempt_freeze = (options & HEAP_PAGE_PRUNE_FREEZE) != 0;
prstate.cutoffs = cutoffs; prstate.cutoffs = cutoffs;
/* /*
@@ -395,7 +510,7 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer,
/* initialize page freezing working state */ /* initialize page freezing working state */
prstate.pagefrz.freeze_required = false; prstate.pagefrz.freeze_required = false;
if (prstate.freeze) if (prstate.attempt_freeze)
{ {
Assert(new_relfrozen_xid && new_relmin_mxid); Assert(new_relfrozen_xid && new_relmin_mxid);
prstate.pagefrz.FreezePageRelfrozenXid = *new_relfrozen_xid; prstate.pagefrz.FreezePageRelfrozenXid = *new_relfrozen_xid;
@@ -442,7 +557,7 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer,
* function, when we return the value to the caller, so that the caller * function, when we return the value to the caller, so that the caller
* doesn't set the VM bit incorrectly. * doesn't set the VM bit incorrectly.
*/ */
if (prstate.freeze) if (prstate.attempt_freeze)
{ {
prstate.all_visible = true; prstate.all_visible = true;
prstate.all_frozen = true; prstate.all_frozen = true;
@@ -556,7 +671,7 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer,
* If checksums are enabled, heap_prune_satisfies_vacuum() may have caused * If checksums are enabled, heap_prune_satisfies_vacuum() may have caused
* an FPI to be emitted. * an FPI to be emitted.
*/ */
hint_bit_fpi = fpi_before != pgWalUsage.wal_fpi; did_tuple_hint_fpi = fpi_before != pgWalUsage.wal_fpi;
/* /*
* Process HOT chains. * Process HOT chains.
@@ -664,97 +779,23 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer,
* pd_prune_xid field or the page was marked full, we will update the hint * pd_prune_xid field or the page was marked full, we will update the hint
* bit. * bit.
*/ */
do_hint = ((PageHeader) page)->pd_prune_xid != prstate.new_prune_xid || do_hint_prune = ((PageHeader) page)->pd_prune_xid != prstate.new_prune_xid ||
PageIsFull(page); PageIsFull(page);
/* /*
* Decide if we want to go ahead with freezing according to the freeze * Decide if we want to go ahead with freezing according to the freeze
* plans we prepared, or not. * plans we prepared, or not.
*/ */
do_freeze = false; do_freeze = heap_page_will_freeze(relation, buffer,
if (prstate.freeze) did_tuple_hint_fpi,
{ do_prune,
if (prstate.pagefrz.freeze_required) do_hint_prune,
{ &prstate);
/*
* heap_prepare_freeze_tuple indicated that at least one XID/MXID
* from before FreezeLimit/MultiXactCutoff is present. Must
* freeze to advance relfrozenxid/relminmxid.
*/
do_freeze = true;
}
else
{
/*
* Opportunistically freeze the page if we are generating an FPI
* anyway and if doing so means that we can set the page
* all-frozen afterwards (might not happen until VACUUM's final
* heap pass).
*
* XXX: Previously, we knew if pruning emitted an FPI by checking
* pgWalUsage.wal_fpi before and after pruning. Once the freeze
* and prune records were combined, this heuristic couldn't be
* used anymore. The opportunistic freeze heuristic must be
* improved; however, for now, try to approximate the old logic.
*/
if (prstate.all_visible && prstate.all_frozen && prstate.nfrozen > 0)
{
/*
* Freezing would make the page all-frozen. Have already
* emitted an FPI or will do so anyway?
*/
if (RelationNeedsWAL(relation))
{
if (hint_bit_fpi)
do_freeze = true;
else if (do_prune)
{
if (XLogCheckBufferNeedsBackup(buffer))
do_freeze = true;
}
else if (do_hint)
{
if (XLogHintBitIsNeeded() && XLogCheckBufferNeedsBackup(buffer))
do_freeze = true;
}
}
}
}
}
if (do_freeze)
{
/*
* Validate the tuples we will be freezing before entering the
* critical section.
*/
heap_pre_freeze_checks(buffer, prstate.frozen, prstate.nfrozen);
}
else if (prstate.nfrozen > 0)
{
/*
* The page contained some tuples that were not already frozen, and we
* chose not to freeze them now. The page won't be all-frozen then.
*/
Assert(!prstate.pagefrz.freeze_required);
prstate.all_frozen = false;
prstate.nfrozen = 0; /* avoid miscounts in instrumentation */
}
else
{
/*
* We have no freeze plans to execute. The page might already be
* all-frozen (perhaps only following pruning), though. Such pages
* can be marked all-frozen in the VM by our caller, even though none
* of its tuples were newly frozen here.
*/
}
/* Any error while applying the changes is critical */ /* Any error while applying the changes is critical */
START_CRIT_SECTION(); START_CRIT_SECTION();
if (do_hint) if (do_hint_prune)
{ {
/* /*
* Update the page's pd_prune_xid field to either zero, or the lowest * Update the page's pd_prune_xid field to either zero, or the lowest
@@ -897,7 +938,7 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer,
presult->lpdead_items = prstate.lpdead_items; presult->lpdead_items = prstate.lpdead_items;
/* the presult->deadoffsets array was already filled in */ /* the presult->deadoffsets array was already filled in */
if (prstate.freeze) if (prstate.attempt_freeze)
{ {
if (presult->nfrozen > 0) if (presult->nfrozen > 0)
{ {
@@ -1479,7 +1520,7 @@ heap_prune_record_unchanged_lp_normal(Page page, PruneState *prstate, OffsetNumb
} }
/* Consider freezing any normal tuples which will not be removed */ /* Consider freezing any normal tuples which will not be removed */
if (prstate->freeze) if (prstate->attempt_freeze)
{ {
bool totally_frozen; bool totally_frozen;