From 05f4ea29319637df578a92e90df5d24919cc2f79 Mon Sep 17 00:00:00 2001
From: Zhijie Hou <houzj.fnst@fujitsu.com>
Date: Tue, 3 Mar 2026 11:44:38 +0800
Subject: [PATCH v1] Advance restart_lsn when reaching consistency without
 waiting

Currently, the replication slot's restart_lsn is not advanced when first time
building a consistent snapshot, even when it's safe to do so. This can lead
to unnecessary retention of WAL segments, though the impact is rare.

This commit advances restart_lsn at the consistency point if either:
a serialized snapshot from a previous decoding session is available, or
there were no running transactions when reaching consistency

In both cases, it's safe and efficient to restart decoding from this LSN,
reducing WAL retention without affecting decoding capabilities.
---
 src/backend/replication/logical/snapbuild.c | 27 +++++++++++++++------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 7f79621b57e..490b948267b 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -1136,6 +1136,7 @@ SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xact
 {
 	ReorderBufferTXN *txn;
 	TransactionId xmin;
+	bool	snapshot_built_immediately = false;
 
 	/*
 	 * If we're not consistent yet, inspect the record to see whether it
@@ -1143,11 +1144,7 @@ SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xact
 	 * our snapshot so others or we, after a restart, can use it.
 	 */
 	if (builder->state < SNAPBUILD_CONSISTENT)
-	{
-		/* returns false if there's no point in performing cleanup just yet */
-		if (!SnapBuildFindSnapshot(builder, lsn, running))
-			return;
-	}
+		snapshot_built_immediately = !SnapBuildFindSnapshot(builder, lsn, running);
 	else
 		SnapBuildSerialize(builder, lsn);
 
@@ -1165,8 +1162,15 @@ SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xact
 	 */
 	builder->xmin = running->oldestRunningXid;
 
-	/* Remove transactions we don't need to keep track off anymore */
-	SnapBuildPurgeOlderTxn(builder);
+	/*
+	 * Remove transactions we don't need to keep track off anymore.
+	 *
+	 * Cleanup is skipped if this is the first time we built a consistent
+	 * snapshot and we didn't wait for any transactions. In that case, no
+	 * transaction data has accumulated.
+	 */
+	if (!snapshot_built_immediately)
+		SnapBuildPurgeOlderTxn(builder);
 
 	/*
 	 * Advance the xmin limit for the current replication slot, to allow
@@ -1211,7 +1215,6 @@ SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xact
 	 */
 	if (txn != NULL && XLogRecPtrIsValid(txn->restart_decoding_lsn))
 		LogicalIncreaseRestartDecodingForSlot(lsn, txn->restart_decoding_lsn);
-
 	/*
 	 * No in-progress transaction, can reuse the last serialized snapshot if
 	 * we have one.
@@ -1221,6 +1224,14 @@ SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xact
 			 XLogRecPtrIsValid(builder->last_serialized_snapshot))
 		LogicalIncreaseRestartDecodingForSlot(lsn,
 											  builder->last_serialized_snapshot);
+	/*
+	 * If we built a snapshot immediately at this LSN, either a serialized
+	 * snapshot from a different decoding session is available or there were no
+	 * running transactions. In either case, it's safe and efficient to restart
+	 * from this LSN next time.
+	 */
+	else if (snapshot_built_immediately)
+		LogicalIncreaseRestartDecodingForSlot(lsn, lsn);
 }
 
 
-- 
2.51.1.windows.1

