On Mon, Sep 21, 2026 at 12:18 PM Melanie Plageman
<[email protected]> wrote:
>
> I've tightened up the commit messages in the latest version and
> changed 0002 as you suggested. I'll commit 0001-0003 after beta4 is
> tagged tomorrow. 0004 I'm going to think about just a bit longer (and
> would be master only).

I've committed all but v2-0004. That turned out to have a bug where I
updated the prune_xid to newest_live_xid even when there were dead
items (because that code runs before we set all-visible to false due
to dead items. Attached v3 fixes that and has a patch to replace
foreach with foreach_int and foreach_node which David suggested a long
time ago and I never got around to. 0001 targets master only, 0002
targets 19 and master. I'll commit 0002 very shortly since it's so
trivial.

- Melanie
From 41b2088e4e781cb0f403d6429a05559543831b7a Mon Sep 17 00:00:00 2001
From: Melanie Plageman <[email protected]>
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 <[email protected]>
Discussion: https://postgr.es/m/CAAKRu_amj7qLF4c=9ijd=708Fu2G8gg-2EqwBu=acdahu2s...@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

From 96f690abe32900bfeeb50bfe52eda63e52baab98 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <[email protected]>
Date: Wed, 23 Sep 2026 17:15:51 -0400
Subject: [PATCH v3 2/2] Use typed foreach macros for planner relation lists

Use foreach_int() and foreach_node() when building the result-relation
and row-mark bitmapsets in standard_planner(). This avoids manual
ListCell extraction and removes an unnecessary iterator declaration.

Suggested-by: David Rowley <[email protected]>

Discussion: https://postgr.es/m/CAApHDvq_R-gNXu%2B06GQW6w_HaEMh1pezsyiCh7GNhgh%2Bh0UqMw%40mail.gmail.com
---
 src/backend/optimizer/plan/planner.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 55a35aa3397..3580bbc21b4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -364,8 +364,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	Path	   *best_path;
 	Plan	   *top_plan;
 	ListCell   *lp,
-			   *lr,
-			   *lc;
+			   *lr;
 
 	/*
 	 * Set up global state for this planner invocation.  This data is needed
@@ -689,12 +688,12 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	 * Compute resultRelationRelids and rowMarkRelids from resultRelations and
 	 * rowMarks. These can be used for cheap membership checks.
 	 */
-	foreach(lc, glob->resultRelations)
+	foreach_int(rti, glob->resultRelations)
 		result->resultRelationRelids = bms_add_member(result->resultRelationRelids,
-													  lfirst_int(lc));
-	foreach(lc, glob->finalrowmarks)
+													  rti);
+	foreach_node(PlanRowMark, rowmark, glob->finalrowmarks)
 		result->rowMarkRelids = bms_add_member(result->rowMarkRelids,
-											   ((PlanRowMark *) lfirst(lc))->rti);
+												   rowmark->rti);
 
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
-- 
2.43.0

Reply via email to