]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Update FSM during prune/freeze replay even if freespace is zero
authorMelanie Plageman <melanieplageman@gmail.com>
Thu, 16 Apr 2026 16:10:47 +0000 (12:10 -0400)
committerMelanie Plageman <melanieplageman@gmail.com>
Thu, 16 Apr 2026 16:10:47 +0000 (12:10 -0400)
add323da40a started updating the visibility map in the same WAL record
as pruning and freezing. This included updating the freespace map during
replay of a record setting the VM, which we've done since ab7dbd681.

add323da40a, however, conditioned doing so on there being > 0 freespace
on the page, which differed from the previous state for records updating
the VM.

The FSM is not WAL-logged and is instead updated heuristically on
standbys. In rare cases, this heuristic could lead to pages with 0
freespace having outdated entries in the FSM. If the standby is later
promoted and vacuum skips these pages because they are marked
all-visible/all-frozen, overly optimistic values would be propagated up
the FSM tree, causing slowness when searching for freespace for new
tuples.

Fix it by always updating the FSM during replay when setting VM bits.

Author: Melanie Plageman <melanieplageman@gmail.com>
Reported-by: Alexey Makhmutov <a.makhmutov@postgrespro.ru>
Discussion: https://postgr.es/m/ead2f110-c736-48f5-99e1-023dc9acbf0b%40postgrespro.ru

src/backend/access/heap/heapam_xlog.c

index f3f419d3dc195da32422c920540ee4e2570dff83..9ed7024e81474c1c06f80e203ad51f26a53eaf8c 100644 (file)
@@ -38,6 +38,7 @@ heap_xlog_prune_freeze(XLogReaderState *record)
        Buffer          vmbuffer = InvalidBuffer;
        uint8           vmflags = 0;
        Size            freespace = 0;
+       bool            do_update_fsm = false;
 
        XLogRecGetBlockTag(record, 0, &rlocator, NULL, &blkno);
        memcpy(&xlrec, maindataptr, SizeOfHeapPrune);
@@ -211,7 +212,10 @@ heap_xlog_prune_freeze(XLogReaderState *record)
                                                        XLHP_HAS_DEAD_ITEMS |
                                                        XLHP_HAS_NOW_UNUSED_ITEMS)) ||
                        (vmflags & VISIBILITYMAP_VALID_BITS))
+               {
                        freespace = PageGetHeapFreeSpace(BufferGetPage(buffer));
+                       do_update_fsm = true;
+               }
 
                /*
                 * We want to avoid holding an exclusive lock on the heap buffer while
@@ -248,7 +252,7 @@ heap_xlog_prune_freeze(XLogReaderState *record)
        if (BufferIsValid(vmbuffer))
                UnlockReleaseBuffer(vmbuffer);
 
-       if (freespace > 0)
+       if (do_update_fsm)
                XLogRecordPageWithFreeSpace(rlocator, blkno, freespace);
 }