From 0bda92f7fe32f24b7b9b0b9caaac111c79c3b434 Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Thu, 24 Sep 2026 16:42:36 -0400 Subject: [PATCH v2 3/3] Detect and repair a stale all-frozen visibility map bit visibilitymap_set() does not clear any bits, so if vacuum finds that every tuple on the page is visible but not every tuple is frozen and yet the page is marked all-frozen in the VM, it wouldn't repair it. Add a VM_CORRUPT_STALE_ALL_FROZEN case to heap_page_fix_vm_corruption() that will clear the VM bits. The normal path will then set the all-visible bit separately. After this change, we can be sure that visibilitymap_set() will modify the VM buffer and mark it dirty. Backpatch-through: 19 --- src/backend/access/heap/pruneheap.c | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index 833f6f0395c..2ddcbecff3a 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -196,6 +196,8 @@ typedef enum VMCorruptionType VM_CORRUPT_LPDEAD, /* Tuple not visible to all transactions on a page marked all-visible */ VM_CORRUPT_TUPLE_VISIBILITY, + /* Page marked all-frozen in the VM but not actually all-frozen */ + VM_CORRUPT_STALE_ALL_FROZEN, } VMCorruptionType; /* Local functions */ @@ -944,6 +946,23 @@ heap_page_fix_vm_corruption(PruneState *prstate, OffsetNumber offnum, relname, prstate->block))); do_clear_vm = true; break; + + case VM_CORRUPT_STALE_ALL_FROZEN: + + /* + * We examined every tuple on the page and found that the page is + * all-visible but not all-frozen, yet the VM marks it all-frozen. + * The page-level PD_ALL_VISIBLE flag is still correct, so only + * the VM is wrong. Clear both the VM bits. All-visible will be + * set again through the normal path. + */ + ereport(WARNING, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("page marked all-frozen in the visibility map is not all-frozen"), + errcontext("relation \"%s\", page %u", + relname, prstate->block))); + do_clear_vm = true; + break; } Assert(do_clear_heap || do_clear_vm); @@ -1259,6 +1278,20 @@ heap_page_prune_and_freeze(PruneFreezeParams *params, Assert(!prstate.set_all_visible || prstate.attempt_set_vm); Assert(!prstate.set_all_visible || (prstate.lpdead_items == 0)); + /* + * If we examined every tuple on the page and found that the page is + * all-visible but not all-frozen, yet the VM marks it all-frozen, that + * all-frozen bit is corrupt. Repair the VM. We will set it back to + * all-visible later along with the other changes. Note that this must be + * done after set_all_visible and set_all_frozen are finalized above to + * account for dead items and unfrozen tuples. + */ + if (prstate.attempt_freeze && prstate.set_all_visible && + !prstate.set_all_frozen && + (prstate.old_vmbits & VISIBILITYMAP_ALL_FROZEN)) + heap_page_fix_vm_corruption(&prstate, InvalidOffsetNumber, + VM_CORRUPT_STALE_ALL_FROZEN); + do_set_vm = heap_page_will_set_vm(&prstate, params->reason, do_prune, do_freeze); /* -- 2.43.0