]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
WAL log VM setting for empty pages in XLOG_HEAP2_PRUNE_VACUUM_SCAN
authorMelanie Plageman <melanieplageman@gmail.com>
Tue, 24 Mar 2026 21:28:05 +0000 (17:28 -0400)
committerMelanie Plageman <melanieplageman@gmail.com>
Tue, 24 Mar 2026 21:30:54 +0000 (17:30 -0400)
As part of removing XLOG_HEAP2_VISIBLE records, phase I of VACUUM now
marks empty pages all-visible and all-frozen in a
XLOG_HEAP2_PRUNE_VACUUM_SCAN record.

This has no real independent benefit, but empty pages were the last user
of XLOG_HEAP2_VISIBLE, so by making this change we can next remove all
of the XLOG_HEAP2_VISIBLE code.

Author: Melanie Plageman <melanieplageman@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Kirill Reshke <reshkekirill@gmail.com>
Earlier version Reviewed-by: Robert Haas <robertmhaas@gmail.com>

src/backend/access/heap/pruneheap.c
src/backend/access/heap/vacuumlazy.c

index f31aa5fdffd35a9785dd8ef9b467e3ac7212e27f..42f0b10edc1ef4c4c21ce2f12f28988f0b5ed88b 100644 (file)
@@ -2541,6 +2541,8 @@ log_heap_prune_and_freeze(Relation relation, Buffer buffer,
        uint8           info;
        uint8           regbuf_flags_heap;
 
+       Page            heap_page = BufferGetPage(buffer);
+
        /* The following local variables hold data registered in the WAL record: */
        xlhp_freeze_plan plans[MaxHeapTuplesPerPage];
        xlhp_freeze_plans freeze_plans;
@@ -2550,6 +2552,7 @@ log_heap_prune_and_freeze(Relation relation, Buffer buffer,
        OffsetNumber frz_offsets[MaxHeapTuplesPerPage];
        bool            do_prune = nredirected > 0 || ndead > 0 || nunused > 0;
        bool            do_set_vm = vmflags & VISIBILITYMAP_VALID_BITS;
+       bool            heap_fpi_allowed = true;
 
        Assert((vmflags & VISIBILITYMAP_VALID_BITS) == vmflags);
 
@@ -2559,15 +2562,22 @@ log_heap_prune_and_freeze(Relation relation, Buffer buffer,
        /*
         * We can avoid an FPI of the heap page if the only modification we are
         * making to it is to set PD_ALL_VISIBLE and checksums/wal_log_hints are
-        * disabled. Note that if we explicitly skip an FPI, we must not stamp the
-        * heap page with this record's LSN. Recovery skips records <= the stamped
-        * LSN, so this could lead to skipping an earlier FPI needed to repair a
-        * torn page.
+        * disabled.
+        *
+        * However, if the page has never been WAL-logged (LSN is invalid), we
+        * must force an FPI regardless.  This can happen when another backend
+        * extends the heap, initializes the page, and then fails before WAL-
+        * logging it.  Since heap extension is not WAL-logged, recovery might try
+        * to replay our record and find that the page isn't initialized, which
+        * would cause a PANIC.
         */
-       if (!do_prune &&
-               nfrozen == 0 &&
-               (!do_set_vm || !XLogHintBitIsNeeded()))
+       if (!XLogRecPtrIsValid(PageGetLSN(heap_page)))
+               regbuf_flags_heap |= REGBUF_FORCE_IMAGE;
+       else if (!do_prune && nfrozen == 0 && (!do_set_vm || !XLogHintBitIsNeeded()))
+       {
                regbuf_flags_heap |= REGBUF_NO_IMAGE;
+               heap_fpi_allowed = false;
+       }
 
        /*
         * Prepare data for the buffer.  The arrays are not actually in the
@@ -2681,12 +2691,13 @@ log_heap_prune_and_freeze(Relation relation, Buffer buffer,
        }
 
        /*
-        * See comment at the top of the function about regbuf_flags_heap for
-        * details on when we can advance the page LSN.
+        * If we explicitly skip an FPI, we must not stamp the heap page with this
+        * record's LSN. Recovery skips records <= the stamped LSN, so this could
+        * lead to skipping an earlier FPI needed to repair a torn page.
         */
-       if (do_prune || nfrozen > 0 || (do_set_vm && XLogHintBitIsNeeded()))
+       if (heap_fpi_allowed)
        {
                Assert(BufferIsDirty(buffer));
-               PageSetLSN(BufferGetPage(buffer), recptr);
+               PageSetLSN(heap_page, recptr);
        }
 }
index 23deabd8c0169f25986c2b396c8492c4f47b7c1a..63e6199241cb486b1b9f93b1ac2617dad9c81f1b 100644 (file)
@@ -1929,33 +1929,43 @@ lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf, BlockNumber blkno,
                 */
                if (!PageIsAllVisible(page))
                {
+                       /* Lock vmbuffer before entering critical section */
+                       LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE);
+
                        START_CRIT_SECTION();
 
                        /* mark buffer dirty before writing a WAL record */
                        MarkBufferDirty(buf);
 
+                       PageSetAllVisible(page);
+                       PageClearPrunable(page);
+                       visibilitymap_set_vmbits(blkno,
+                                                                        vmbuffer,
+                                                                        VISIBILITYMAP_ALL_VISIBLE |
+                                                                        VISIBILITYMAP_ALL_FROZEN,
+                                                                        vacrel->rel->rd_locator);
+
                        /*
-                        * It's possible that another backend has extended the heap,
-                        * initialized the page, and then failed to WAL-log the page due
-                        * to an ERROR.  Since heap extension is not WAL-logged, recovery
-                        * might try to replay our record setting the page all-visible and
-                        * find that the page isn't initialized, which will cause a PANIC.
-                        * To prevent that, check whether the page has been previously
-                        * WAL-logged, and if not, do that now.
+                        * Emit WAL for setting PD_ALL_VISIBLE on the heap page and
+                        * setting the VM.
                         */
-                       if (RelationNeedsWAL(vacrel->rel) &&
-                               !XLogRecPtrIsValid(PageGetLSN(page)))
-                               log_newpage_buffer(buf, true);
+                       if (RelationNeedsWAL(vacrel->rel))
+                               log_heap_prune_and_freeze(vacrel->rel, buf,
+                                                                                 vmbuffer,
+                                                                                 VISIBILITYMAP_ALL_VISIBLE |
+                                                                                 VISIBILITYMAP_ALL_FROZEN,
+                                                                                 InvalidTransactionId, /* conflict xid */
+                                                                                 false,        /* cleanup lock */
+                                                                                 PRUNE_VACUUM_SCAN,    /* reason */
+                                                                                 NULL, 0,
+                                                                                 NULL, 0,
+                                                                                 NULL, 0,
+                                                                                 NULL, 0);
 
-                       PageSetAllVisible(page);
-                       PageClearPrunable(page);
-                       visibilitymap_set(vacrel->rel, blkno, buf,
-                                                         InvalidXLogRecPtr,
-                                                         vmbuffer, InvalidTransactionId,
-                                                         VISIBILITYMAP_ALL_VISIBLE |
-                                                         VISIBILITYMAP_ALL_FROZEN);
                        END_CRIT_SECTION();
 
+                       LockBuffer(vmbuffer, BUFFER_LOCK_UNLOCK);
+
                        /* Count the newly all-frozen pages for logging */
                        vacrel->new_all_visible_pages++;
                        vacrel->new_all_visible_all_frozen_pages++;