From 9261cd1a31e7f9cb63cbffa36c330a8c6e114810 Mon Sep 17 00:00:00 2001
From: Jeff Davis <jeff@j-davis.com>
Date: Fri, 6 Nov 2026 13:41:57 -0800
Subject: [PATCH v1 1/2] Recovery: ensure CLOG updates wait for
 minRecoveryPoint.

The hot_standby=on case already used TransactionIdAsyncCommitTree();
use that for hot_standby=off, too.

Before this commit, it was possible that the CLOG status could reach
disk without advancing minRecoveryPoint. Recovering with PITR prior to
the transaction's commit record could incorrectly see the transaction
as committed.

Backpatch-through: 14
---
 src/backend/access/transam/xact.c | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 49e64939667..990440a964a 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -6243,9 +6243,14 @@ xact_redo_commit(xl_xact_parsed_commit *parsed,
 	if (standbyState == STANDBY_DISABLED)
 	{
 		/*
-		 * Mark the transaction committed in pg_xact.
+		 * Mark the transaction committed in pg_xact. We use the async commit
+		 * protocol during recovery to ensure that minRecoveryPoint is
+		 * advanced before the CLOG update or any hint bit updates are written
+		 * to disk. This ensures that if we crash, we don't see the committed
+		 * status or hint bits set for transactions that haven't yet
+		 * recovered.
 		 */
-		TransactionIdCommitTree(xid, parsed->nsubxacts, parsed->subxacts);
+		TransactionIdAsyncCommitTree(xid, parsed->nsubxacts, parsed->subxacts, lsn);
 	}
 	else
 	{
@@ -6260,15 +6265,7 @@ xact_redo_commit(xl_xact_parsed_commit *parsed,
 		 */
 		RecordKnownAssignedTransactionIds(max_xid);
 
-		/*
-		 * Mark the transaction committed in pg_xact. We use async commit
-		 * protocol during recovery to provide information on database
-		 * consistency for when users try to set hint bits. It is important
-		 * that we do not set hint bits until the minRecoveryPoint is past
-		 * this commit record. This ensures that if we crash we don't see hint
-		 * bits set on changes made by transactions that haven't yet
-		 * recovered. It's unlikely but it's good to be safe.
-		 */
+		/* use async, the same as the STANDBY_DISABLED case */
 		TransactionIdAsyncCommitTree(xid, parsed->nsubxacts, parsed->subxacts, lsn);
 
 		/*
-- 
2.53.0

