From 5cbb8e5806a444df04ed2f54d1c45f4aa1ea8f7b Mon Sep 17 00:00:00 2001
From: Mingwei Jia <wei19860922@163.com>
Date: Sat, 12 Apr 2025 21:10:44 +0800
Subject: [PATCH] If the checkpoint's oldestActiveXid at standby startup is
 less than nextXid, then the visibility of transactions within that range
 should be determined using the CLOG. This is because there may be
 transactions in that range that have already committed on the primary, and
 these committed transactions will not be replayed again on the standby.

---
 src/backend/access/transam/xlog.c             |  2 ++
 src/backend/storage/ipc/procarray.c           |  4 +++
 src/backend/utils/time/snapmgr.c              | 12 ++++++++
 src/include/access/transam.h                  |  2 ++
 src/include/storage/procarray.h               |  2 ++
 .../modules/test_misc/t/008_csnstandby.pl     | 30 +++++++++++++++++++
 6 files changed, 52 insertions(+)
 create mode 100644 src/test/modules/test_misc/t/008_csnstandby.pl

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index deb2cd1883c..2796458491d 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -5565,6 +5565,8 @@ StartupXLOG(void)
 	TransamVariables->nextXid = checkPoint.nextXid;
 	TransamVariables->nextOid = checkPoint.nextOid;
 	TransamVariables->oidCount = 0;
+	TransamVariables->nextXidStandbyStart =
+					XidFromFullTransactionId(checkPoint.nextXid);
 	MultiXactSetNextMXact(checkPoint.nextMulti, checkPoint.nextMultiOffset);
 	AdvanceOldestClogXid(checkPoint.oldestXid);
 	SetTransactionIdLimit(checkPoint.oldestXid, checkPoint.oldestXidDB);
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index c82e8d8c438..d4c67512c56 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -962,6 +962,10 @@ ProcArrayUpdateOldestRunningXid(TransactionId oldestRunningXID)
 	procArray->oldest_running_primary_xid = oldestRunningXID;
 	LWLockRelease(ProcArrayLock);
 }
+TransactionId GetOldestRunningXid(void)
+{
+	return 	procArray->oldest_running_primary_xid;
+}
 
 
 /*
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index df9e8ba37f4..0ebcb0af835 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -1969,6 +1969,18 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
 		uint32		cache_idx = snapshot->xmax - xid;
 		uint64		wordno = cache_idx / INPROGRESS_CACHE_XIDS_PER_WORD;
 		uint64		slotno = (cache_idx % INPROGRESS_CACHE_XIDS_PER_WORD) * INPROGRESS_CACHE_BITS;
+		TransactionId nextXidStart = TransamVariables->nextXidStandbyStart;
+		TransactionId oldestRunning = GetOldestRunningXid();
+
+		if (TransactionIdPrecedes(oldestRunning, nextXidStart)
+			&& TransactionIdPrecedes(xid, nextXidStart))
+		{
+			if (TransactionIdDidCommit(xid) || TransactionIdDidAbort(xid))
+			{
+				return false;
+			}
+			return true;
+		}
 
 		if (snapshot->inprogress_cache)
 		{
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index a7054fe11cd..a75d66f4d40 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -240,6 +240,8 @@ typedef struct TransamVariablesData
 
 	/* During recovery, LSN of latest replayed commit record */
 	XLogRecPtr	latestCommitLSN;
+	/* checkpoint`s next xid when hot-standby start */
+	TransactionId nextXidStandbyStart;
 
 	/*
 	 * Number of top-level transactions with xids (i.e. which may have
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index de74fce24e4..a4129de8101 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -29,6 +29,8 @@ extern void ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid);
 extern void ProcArrayClearTransaction(PGPROC *proc);
 
 extern void ProcArrayUpdateOldestRunningXid(TransactionId oldestRunningXID);
+
+extern TransactionId GetOldestRunningXid(void);
 extern void ProcArrayInitRecovery(TransactionId initializedUptoXID);
 
 extern void RecordKnownAssignedTransactionIds(TransactionId xid);
diff --git a/src/test/modules/test_misc/t/008_csnstandby.pl b/src/test/modules/test_misc/t/008_csnstandby.pl
new file mode 100644
index 00000000000..98708490d91
--- /dev/null
+++ b/src/test/modules/test_misc/t/008_csnstandby.pl
@@ -0,0 +1,30 @@
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init();
+$primary->append_conf('postgresql.conf', 'max_wal_senders = 5');
+$primary->append_conf('postgresql.conf', 'wal_level=replica');
+$primary->start;
+my $count = '';
+$primary->safe_psql('postgres', 'create table t1(i int, j int)');
+
+my $primary_a =  $primary->background_psql('postgres', on_error_die => 1);
+$primary_a->query_safe("begin");
+$primary_a->query_safe("insert into t1 values(1,1)");
+$primary->safe_psql('postgres', 'insert into t1 values(2,1)');
+$primary->backup('bkp');
+
+my $replica = PostgreSQL::Test::Cluster->new('replica');
+$replica->init_from_backup($primary, 'bkp', has_streaming => 1);
+$replica->start;
+
+$count = $replica->safe_psql('postgres', "select count(*) from t1");
+is($count, '1', "get right visiablity before primary checkpoint in hot standby");
+
+done_testing();
+
--
2.45.0

