Combine FSM updates for prune and no-prune cases.
authorRobert Haas <rhaas@postgresql.org>
Fri, 26 Jan 2024 16:40:16 +0000 (11:40 -0500)
committerRobert Haas <rhaas@postgresql.org>
Fri, 26 Jan 2024 16:40:16 +0000 (11:40 -0500)
lazy_scan_prune() and lazy_scan_noprune() update the freespace map
with identical conditions; combine them. This consolidation is easier
now that cb970240f13df2b63f0410f81f452179a2b78d6f moved visibility map
updates into lazy_scan_prune().

While combining the FSM updates, simplify the logic for calling
lazy_scan_new_or_empty() and lazy_scan_noprune().

Also update a few comemnts in this part of the code to make them,
hopefully, clearer.

Melanie Plageman and Robert Haas

Discussion: https://postgr.es/m/CA%2BTgmoaLTvipm%3Dxx4rJLr07m908PCu%3DQH3uCjD1UOn8YaEuO2g%40mail.gmail.com

src/backend/access/heap/vacuumlazy.c

index 0fb3953513d7494c6d58185abd9e00d64a8d3514..fa56480808bf3c728723015a4c884ed84fcb961a 100644 (file)
@@ -838,6 +838,7 @@ lazy_scan_heap(LVRelState *vacrel)
        Page        page;
        bool        all_visible_according_to_vm;
        bool        has_lpdead_items;
+       bool        got_cleanup_lock = false;
 
        if (blkno == next_unskippable_block)
        {
@@ -931,63 +932,40 @@ lazy_scan_heap(LVRelState *vacrel)
         */
        visibilitymap_pin(vacrel->rel, blkno, &vmbuffer);
 
+       buf = ReadBufferExtended(vacrel->rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
+                                vacrel->bstrategy);
+       page = BufferGetPage(buf);
+
        /*
         * We need a buffer cleanup lock to prune HOT chains and defragment
         * the page in lazy_scan_prune.  But when it's not possible to acquire
         * a cleanup lock right away, we may be able to settle for reduced
         * processing using lazy_scan_noprune.
         */
-       buf = ReadBufferExtended(vacrel->rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
-                                vacrel->bstrategy);
-       page = BufferGetPage(buf);
-       if (!ConditionalLockBufferForCleanup(buf))
-       {
-           LockBuffer(buf, BUFFER_LOCK_SHARE);
+       got_cleanup_lock = ConditionalLockBufferForCleanup(buf);
 
-           /* Check for new or empty pages before lazy_scan_noprune call */
-           if (lazy_scan_new_or_empty(vacrel, buf, blkno, page, true,
-                                      vmbuffer))
-           {
-               /* Processed as new/empty page (lock and pin released) */
-               continue;
-           }
-
-           /*
-            * Collect LP_DEAD items in dead_items array, count tuples,
-            * determine if rel truncation is safe
-            */
-           if (lazy_scan_noprune(vacrel, buf, blkno, page, &has_lpdead_items))
-           {
-               Size        freespace = 0;
-               bool        recordfreespace;
+       if (!got_cleanup_lock)
+           LockBuffer(buf, BUFFER_LOCK_SHARE);
 
-               /*
-                * We processed the page successfully (without a cleanup
-                * lock).
-                *
-                * Update the FSM, just as we would in the case where
-                * lazy_scan_prune() is called. Our goal is to update the
-                * freespace map the last time we touch the page. If the
-                * relation has no indexes, or if index vacuuming is disabled,
-                * there will be no second heap pass; if this particular page
-                * has no dead items, the second heap pass will not touch this
-                * page. So, in those cases, update the FSM now.
-                *
-                * After a call to lazy_scan_prune(), we would also try to
-                * adjust the page-level all-visible bit and the visibility
-                * map, but we skip that step in this path.
-                */
-               recordfreespace = vacrel->nindexes == 0
-                   || !vacrel->do_index_vacuuming
-                   || !has_lpdead_items;
-               if (recordfreespace)
-                   freespace = PageGetHeapFreeSpace(page);
-               UnlockReleaseBuffer(buf);
-               if (recordfreespace)
-                   RecordPageWithFreeSpace(vacrel->rel, blkno, freespace);
-               continue;
-           }
+       /* Check for new or empty pages before lazy_scan_[no]prune call */
+       if (lazy_scan_new_or_empty(vacrel, buf, blkno, page, !got_cleanup_lock,
+                                  vmbuffer))
+       {
+           /* Processed as new/empty page (lock and pin released) */
+           continue;
+       }
 
+       /*
+        * If we didn't get the cleanup lock, we can still collect LP_DEAD
+        * items in the dead_items array for later vacuuming, count live and
+        * recently dead tuples for vacuum logging, and determine if this
+        * block could later be truncated. If we encounter any xid/mxids that
+        * require advancing the relfrozenxid/relminxid, we'll have to wait
+        * for a cleanup lock and call lazy_scan_prune().
+        */
+       if (!got_cleanup_lock &&
+           !lazy_scan_noprune(vacrel, buf, blkno, page, &has_lpdead_items))
+       {
            /*
             * lazy_scan_noprune could not do all required processing.  Wait
             * for a cleanup lock, and call lazy_scan_prune in the usual way.
@@ -995,45 +973,45 @@ lazy_scan_heap(LVRelState *vacrel)
            Assert(vacrel->aggressive);
            LockBuffer(buf, BUFFER_LOCK_UNLOCK);
            LockBufferForCleanup(buf);
-       }
-
-       /* Check for new or empty pages before lazy_scan_prune call */
-       if (lazy_scan_new_or_empty(vacrel, buf, blkno, page, false, vmbuffer))
-       {
-           /* Processed as new/empty page (lock and pin released) */
-           continue;
+           got_cleanup_lock = true;
        }
 
        /*
-        * Prune, freeze, and count tuples.
+        * If we have a cleanup lock, we must now prune, freeze, and count
+        * tuples. We may have acquired the cleanup lock originally, or we may
+        * have gone back and acquired it after lazy_scan_noprune() returned
+        * false. Either way, the page hasn't been processed yet.
         *
-        * Accumulates details of remaining LP_DEAD line pointers on page in
-        * dead_items array.  This includes LP_DEAD line pointers that we
-        * pruned ourselves, as well as existing LP_DEAD line pointers that
-        * were pruned some time earlier.  Also considers freezing XIDs in the
-        * tuple headers of remaining items with storage. It also determines
-        * if truncating this block is safe.
+        * Like lazy_scan_noprune(), lazy_scan_prune() will count
+        * recently_dead_tuples and live tuples for vacuum logging, determine
+        * if the block can later be truncated, and accumulate the details of
+        * remaining LP_DEAD line pointers on the page in the dead_items
+        * array. These dead items include those pruned by lazy_scan_prune()
+        * as well we line pointers previously marked LP_DEAD.
         */
-       lazy_scan_prune(vacrel, buf, blkno, page,
-                       vmbuffer, all_visible_according_to_vm,
-                       &has_lpdead_items);
+       if (got_cleanup_lock)
+           lazy_scan_prune(vacrel, buf, blkno, page,
+                           vmbuffer, all_visible_according_to_vm,
+                           &has_lpdead_items);
 
        /*
-        * Final steps for block: drop cleanup lock, record free space in the
-        * FSM.
+        * Now drop the buffer lock and, potentially, update the FSM.
         *
-        * If we will likely do index vacuuming, wait until
-        * lazy_vacuum_heap_rel() to save free space. This doesn't just save
-        * us some cycles; it also allows us to record any additional free
-        * space that lazy_vacuum_heap_page() will make available in cases
-        * where it's possible to truncate the page's line pointer array.
+        * Our goal is to update the freespace map the last time we touch the
+        * page. If we'll process a block in the second pass, we may free up
+        * additional space on the page, so it is better to update the FSM
+        * after the second pass. If the relation has no indexes, or if index
+        * vacuuming is disabled, there will be no second heap pass; if this
+        * particular page has no dead items, the second heap pass will not
+        * touch this page. So, in those cases, update the FSM now.
         *
-        * Note: It's not in fact 100% certain that we really will call
-        * lazy_vacuum_heap_rel() -- lazy_vacuum() might yet opt to skip index
-        * vacuuming (and so must skip heap vacuuming).  This is deemed okay
-        * because it only happens in emergencies, or when there is very
-        * little free space anyway. (Besides, we start recording free space
-        * in the FSM once index vacuuming has been abandoned.)
+        * Note: In corner cases, it's possible to miss updating the FSM
+        * entirely. If index vacuuming is currently enabled, we'll skip the
+        * FSM update now. But if failsafe mode is later activated, or there
+        * are so few dead tuples that index vacuuming is bypassed, there will
+        * also be no opportunity to update the FSM later, because we'll never
+        * revisit this page. Since updating the FSM is desirable but not
+        * absolutely required, that's OK.
         */
        if (vacrel->nindexes == 0
            || !vacrel->do_index_vacuuming
@@ -1047,9 +1025,10 @@ lazy_scan_heap(LVRelState *vacrel)
            /*
             * Periodically perform FSM vacuuming to make newly-freed space
             * visible on upper FSM pages. This is done after vacuuming if the
-            * table has indexes.
+            * table has indexes. There will only be newly-freed space if we
+            * held the cleanup lock and lazy_scan_prune() was called.
             */
-           if (vacrel->nindexes == 0 && has_lpdead_items &&
+           if (got_cleanup_lock && vacrel->nindexes == 0 && has_lpdead_items &&
                blkno - next_fsm_block_to_vacuum >= VACUUM_FSM_EVERY_PAGES)
            {
                FreeSpaceMapVacuumRange(vacrel->rel, next_fsm_block_to_vacuum,