]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Add helper for freeze determination to heap_page_prune_and_freeze
authorMelanie Plageman <melanieplageman@gmail.com>
Tue, 14 Oct 2025 19:07:40 +0000 (15:07 -0400)
committerMelanie Plageman <melanieplageman@gmail.com>
Tue, 14 Oct 2025 19:08:50 +0000 (15:08 -0400)
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

src/backend/access/heap/pruneheap.c

index a065010a6bcf6fb77a88bcf32efccde7233ad92f..231bea679c68d7a8548a46324ad2b5c6612881a6 100644 (file)
@@ -43,7 +43,7 @@ typedef struct
        /* whether or not dead items can be set LP_UNUSED during pruning */
        bool            mark_unused_now;
        /* whether to attempt freezing tuples */
-       bool            freeze;
+       bool            attempt_freeze;
        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 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.
@@ -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
@@ -366,14 +481,14 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer,
        HeapTupleData tup;
        bool            do_freeze;
        bool            do_prune;
-       bool            do_hint;
-       bool            hint_bit_fpi;
+       bool            do_hint_prune;
+       bool            did_tuple_hint_fpi;
        int64           fpi_before = pgWalUsage.wal_fpi;
 
        /* Copy parameters to prstate */
        prstate.vistest = vistest;
        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;
 
        /*
@@ -395,7 +510,7 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer,
 
        /* initialize page freezing working state */
        prstate.pagefrz.freeze_required = false;
-       if (prstate.freeze)
+       if (prstate.attempt_freeze)
        {
                Assert(new_relfrozen_xid && new_relmin_mxid);
                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
         * doesn't set the VM bit incorrectly.
         */
-       if (prstate.freeze)
+       if (prstate.attempt_freeze)
        {
                prstate.all_visible = 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
         * 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.
@@ -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
         * 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);
 
        /*
         * Decide if we want to go ahead with freezing according to the freeze
         * plans we prepared, or not.
         */
-       do_freeze = false;
-       if (prstate.freeze)
-       {
-               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 (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.
-                */
-       }
+       do_freeze = heap_page_will_freeze(relation, buffer,
+                                                                         did_tuple_hint_fpi,
+                                                                         do_prune,
+                                                                         do_hint_prune,
+                                                                         &prstate);
 
        /* Any error while applying the changes is critical */
        START_CRIT_SECTION();
 
-       if (do_hint)
+       if (do_hint_prune)
        {
                /*
                 * 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;
        /* the presult->deadoffsets array was already filled in */
 
-       if (prstate.freeze)
+       if (prstate.attempt_freeze)
        {
                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 */
-       if (prstate->freeze)
+       if (prstate->attempt_freeze)
        {
                bool            totally_frozen;