v42-0002-Add-pruning-fast-path-for-all-visible-and-all-fr.patch
text/x-patch
Filename: v42-0002-Add-pruning-fast-path-for-all-visible-and-all-fr.patch
Type: text/x-patch
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v42-0002
Subject: Add pruning fast path for all-visible and all-frozen pages
| File | + | − |
|---|---|---|
| src/backend/access/heap/pruneheap.c | 96 | 1 |
| src/backend/access/heap/vacuumlazy.c | 10 | 0 |
| src/include/access/heapam.h | 1 | 0 |
From 4bd5502d07562a0e6ce5cbf315833c5baa676028 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 25 Feb 2026 16:48:19 -0500
Subject: [PATCH v42 02/12] Add pruning fast path for all-visible and
all-frozen pages
Because of the SKIP_PAGES_THRESHOLD optimization or a stale prune XID,
heap_page_prune_and_freeze() can be invoked for pages with no pruning or
freezing work. To avoid this, if a page is already all-frozen or it is
all-visible and no freezing will be attempted, exit early. We can't exit
early if vacuum passed DISABLE_PAGE_SKIPPING, though.
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>
Discussion: https://postgr.es/m/bqc4kh5midfn44gnjiqez3bjqv4zogydguvdn446riw45jcf3y%404ez66il7ebvk
---
src/backend/access/heap/pruneheap.c | 97 +++++++++++++++++++++++++++-
src/backend/access/heap/vacuumlazy.c | 10 +++
src/include/access/heapam.h | 1 +
3 files changed, 107 insertions(+), 1 deletion(-)
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index be3ae21f94c..22f2d9d9798 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -129,6 +129,12 @@ typedef struct
/* Bits in the vmbuffer for this heap page */
uint8 old_vmbits;
+ /*
+ * True if the page can bypass full page inspection during pruning and
+ * freezing based on its visibility map status and the caller's options.
+ */
+ bool fast_path;
+
/*-------------------------------------------------------
* Information about what was done
*
@@ -201,6 +207,7 @@ static void prune_freeze_setup(PruneFreezeParams *params,
PruneState *prstate);
static void heap_page_fix_vm_corruption(PruneState *prstate, OffsetNumber offnum,
VMCorruptionType ctype);
+static void prune_freeze_bypass(PruneState *prstate, PruneFreezeResult *presult);
static void prune_freeze_plan(PruneState *prstate,
OffsetNumber *off_loc);
static HTSV_Result heap_prune_satisfies_vacuum(PruneState *prstate,
@@ -329,7 +336,7 @@ heap_page_prune_opt(Relation relation, Buffer buffer, Buffer *vmbuffer)
* cannot safely determine that during on-access pruning with the
* current implementation.
*/
- params.options = 0;
+ params.options = HEAP_PAGE_PRUNE_ALLOW_FAST_PATH;
heap_page_prune_and_freeze(¶ms, &presult, &dummy_off_loc,
NULL, NULL);
@@ -398,6 +405,16 @@ prune_freeze_setup(PruneFreezeParams *params,
prstate->block,
&prstate->vmbuffer);
+ /*
+ * If the page is already all-frozen, or already all-visible when freezing
+ * is not being attempted, we can skip pruning and freezing entirely.
+ * Callers must opt in by setting HEAP_PAGE_PRUNE_ALLOW_FAST_PATH.
+ */
+ prstate->fast_path = ((prstate->old_vmbits & VISIBILITYMAP_ALL_FROZEN) ||
+ ((prstate->old_vmbits & VISIBILITYMAP_ALL_VISIBLE) &&
+ !prstate->attempt_freeze)) &&
+ (params->options & HEAP_PAGE_PRUNE_ALLOW_FAST_PATH);
+
/*
* Our strategy is to scan the page and make lists of items to change,
* then apply the changes within a critical section. This keeps as much
@@ -915,6 +932,73 @@ heap_page_fix_vm_corruption(PruneState *prstate, OffsetNumber offnum,
}
}
+/*
+ * If the page is already all-frozen, or already all-visible and freezing
+ * is not being attempted, there is no remaining work and we can bypass the
+ * expensive overhead of heap_page_prune_and_freeze().
+ *
+ * This can happen when the page has a stale prune hint, or if VACUUM is
+ * scanning an already all-frozen page due to SKIP_PAGES_THRESHOLD.
+ *
+ * The caller must already have examined the visibility map and saved the
+ * status for the page's VM bits in prstate->old_vmbits. Caller must hold a
+ * content lock on the heap page since it will examine line pointers.
+ *
+ * Before calling prune_freeze_bypass(), the caller should first
+ * check for and fix any discrepancy between the page-level visibility hint
+ * and the visibility map. Otherwise, the fast path will always prevent us
+ * from getting them in sync. Note that if there are tuples on the page that
+ * are not visible to all but the VM is incorrectly marked
+ * all-visible/all-frozen, we will not get the chance to fix that corruption
+ * when using the fast path.
+ */
+static void
+prune_freeze_bypass(PruneState *prstate, PruneFreezeResult *presult)
+{
+ OffsetNumber maxoff = PageGetMaxOffsetNumber(prstate->page);
+ Page page = prstate->page;
+
+ Assert(prstate->old_vmbits & VISIBILITYMAP_ALL_FROZEN ||
+ (prstate->old_vmbits & VISIBILITYMAP_ALL_VISIBLE &&
+ !prstate->attempt_freeze));
+
+ /* We'll fill in presult for the caller */
+ memset(presult, 0, sizeof(PruneFreezeResult));
+
+ presult->old_vmbits = prstate->old_vmbits;
+
+ /* Clear any stale prune hint */
+ if (TransactionIdIsValid(PageGetPruneXid(page)))
+ {
+ PageClearPrunable(page);
+ MarkBufferDirtyHint(prstate->buffer, true);
+ }
+
+ if (PageIsEmpty(page))
+ return;
+
+ /*
+ * Since the page is all-visible, a count of the normal ItemIds on the
+ * page should be sufficient for vacuum's live tuple count.
+ */
+ for (OffsetNumber off = FirstOffsetNumber;
+ off <= maxoff;
+ off = OffsetNumberNext(off))
+ {
+ ItemId lp = PageGetItemId(page, off);
+
+ if (!ItemIdIsUsed(lp))
+ continue;
+
+ presult->hastup = true;
+
+ if (ItemIdIsNormal(lp))
+ prstate->live_tuples++;
+ }
+
+ presult->live_tuples = prstate->live_tuples;
+}
+
/*
* Prune and repair fragmentation and potentially freeze tuples on the
* specified page.
@@ -984,6 +1068,17 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
heap_page_fix_vm_corruption(&prstate, InvalidOffsetNumber,
VM_CORRUPT_MISSING_PAGE_HINT);
+ /*
+ * If the visibility map status allows it, bypass pruning and freezing
+ * entirely. This must be done after fixing any discrepancy between the
+ * page-level visibility hint and the VM.
+ */
+ if (prstate.fast_path)
+ {
+ prune_freeze_bypass(&prstate, presult);
+ return;
+ }
+
/*
* Examine all line pointers and tuple visibility information to determine
* which line pointers should change state and which tuples may be frozen.
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 56722556417..1a446050d85 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -2044,6 +2044,16 @@ lazy_scan_prune(LVRelState *vacrel,
if (vacrel->nindexes == 0)
params.options |= HEAP_PAGE_PRUNE_MARK_UNUSED_NOW;
+ /*
+ * Allow skipping full inspection of pages that the VM indicates are
+ * already all-frozen (which may be scanned due to SKIP_PAGES_THRESHOLD).
+ * However, if DISABLE_PAGE_SKIPPING was specified, we can't trust the VM,
+ * so we must examine the page to make sure it is truly all-frozen and fix
+ * it otherwise.
+ */
+ if (vacrel->skipwithvm)
+ params.options |= HEAP_PAGE_PRUNE_ALLOW_FAST_PATH;
+
heap_page_prune_and_freeze(¶ms,
&presult,
&vacrel->offnum,
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 00134012137..305ecc31a9e 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -42,6 +42,7 @@
/* "options" flag bits for heap_page_prune_and_freeze */
#define HEAP_PAGE_PRUNE_MARK_UNUSED_NOW (1 << 0)
#define HEAP_PAGE_PRUNE_FREEZE (1 << 1)
+#define HEAP_PAGE_PRUNE_ALLOW_FAST_PATH (1 << 2)
typedef struct BulkInsertStateData *BulkInsertState;
typedef struct GlobalVisState GlobalVisState;
--
2.43.0