From 41b2088e4e781cb0f403d6429a05559543831b7a Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Fri, 18 Sep 2026 15:45:03 -0400
Subject: [PATCH v3 1/2] Retain newest live xid as prune hint after visibility
 horizon rejection

When live tuples are too young to mark a page all-visible, pruning can
clear pd_prune_xid and prevent on-access retries after the horizon advances.
Instead, retain the newest live xmin as a retry hint, unless LP_DEAD items
remain. Remaining LP_DEAD items prevent setting the VM until VACUUM
removes them.

VACUUM also records this hint. Setting a previously invalid pd_prune_xid
can dirty an otherwise unchanged page and emit a heap FPI when hint logging
is required; retaining an existing hint can avoid dirtying the page just
to clear it.

Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru>
Discussion: https://postgr.es/m/CAAKRu_amj7qLF4c=9ijd=708Fu2G8gg-2EqwBu=aCdAHU2sPHg@mail.gmail.com
---
 src/backend/access/heap/pruneheap.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 98fba4bb7c1..b541c6d709b 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -1209,8 +1209,20 @@ heap_page_prune_and_freeze(PruneFreezeParams *params,
 		GlobalVisTestXidConsideredRunning(prstate.vistest,
 										  prstate.newest_live_xid,
 										  true))
+	{
 		prstate.set_all_visible = prstate.set_all_frozen = false;
 
+		/*
+		 * Preserve an opportunity to set the VM on-access once the newest
+		 * live xmin is visible to everyone, unless LP_DEAD items remain.
+		 */
+		if (prstate.lpdead_items == 0)
+		{
+			Assert(!TransactionIdIsValid(prstate.new_prune_xid));
+			prstate.new_prune_xid = prstate.newest_live_xid;
+		}
+	}
+
 	/*
 	 * If checksums are enabled, calling heap_prune_satisfies_vacuum() while
 	 * checking tuple visibility information in prune_freeze_plan() may have
-- 
2.43.0

