From 31adb46d5263cd91d358b178139172959c86a113 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <shlok.kyal.oss@gmail.com>
Date: Mon, 8 Sep 2025 21:04:42 +0530
Subject: [PATCH v2_approach2] Add stats related to slot sync skip

When slot sync is performed, it can happen that it is skipped due to
various reason. This patch adds stats for synced slots regarding this
slot sync skip. This patch adds new columns slot_sync_skip_count,
last_slot_sync_skip to view pg_stat_replication_slots and new column
slot_sync_skip_reason to view pg_replication_slots.
---
 contrib/test_decoding/expected/stats.out     | 12 ++--
 doc/src/sgml/monitoring.sgml                 | 20 +++++++
 doc/src/sgml/system-views.sgml               |  8 +++
 src/backend/catalog/system_views.sql         |  5 +-
 src/backend/replication/logical/slotsync.c   | 61 +++++++++++++++++---
 src/backend/replication/slotfuncs.c          | 26 ++++++++-
 src/backend/utils/activity/pgstat_replslot.c | 25 ++++++++
 src/backend/utils/adt/pgstatfuncs.c          | 18 ++++--
 src/include/catalog/pg_proc.dat              | 12 ++--
 src/include/pgstat.h                         |  3 +
 src/include/replication/slot.h               | 17 ++++++
 src/test/regress/expected/rules.out          |  9 ++-
 12 files changed, 188 insertions(+), 28 deletions(-)

diff --git a/contrib/test_decoding/expected/stats.out b/contrib/test_decoding/expected/stats.out
index de6dc416130..c1ff872c08c 100644
--- a/contrib/test_decoding/expected/stats.out
+++ b/contrib/test_decoding/expected/stats.out
@@ -78,17 +78,17 @@ SELECT slot_name, spill_txns = 0 AS spill_txns, spill_count = 0 AS spill_count,
 
 -- verify accessing/resetting stats for non-existent slot does something reasonable
 SELECT * FROM pg_stat_get_replication_slot('do-not-exist');
-  slot_name   | spill_txns | spill_count | spill_bytes | stream_txns | stream_count | stream_bytes | total_txns | total_bytes | stats_reset 
---------------+------------+-------------+-------------+-------------+--------------+--------------+------------+-------------+-------------
- do-not-exist |          0 |           0 |           0 |           0 |            0 |            0 |          0 |           0 | 
+  slot_name   | spill_txns | spill_count | spill_bytes | stream_txns | stream_count | stream_bytes | total_txns | total_bytes | slot_sync_skip_count | last_slot_sync_skip | stats_reset 
+--------------+------------+-------------+-------------+-------------+--------------+--------------+------------+-------------+----------------------+---------------------+-------------
+ do-not-exist |          0 |           0 |           0 |           0 |            0 |            0 |          0 |           0 |                    0 |                     | 
 (1 row)
 
 SELECT pg_stat_reset_replication_slot('do-not-exist');
 ERROR:  replication slot "do-not-exist" does not exist
 SELECT * FROM pg_stat_get_replication_slot('do-not-exist');
-  slot_name   | spill_txns | spill_count | spill_bytes | stream_txns | stream_count | stream_bytes | total_txns | total_bytes | stats_reset 
---------------+------------+-------------+-------------+-------------+--------------+--------------+------------+-------------+-------------
- do-not-exist |          0 |           0 |           0 |           0 |            0 |            0 |          0 |           0 | 
+  slot_name   | spill_txns | spill_count | spill_bytes | stream_txns | stream_count | stream_bytes | total_txns | total_bytes | slot_sync_skip_count | last_slot_sync_skip | stats_reset 
+--------------+------------+-------------+-------------+-------------+--------------+--------------+------------+-------------+----------------------+---------------------+-------------
+ do-not-exist |          0 |           0 |           0 |           0 |            0 |            0 |          0 |           0 |                    0 |                     | 
 (1 row)
 
 -- spilling the xact
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..76bad0d7f2a 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1644,6 +1644,26 @@ description | Waiting for a newly initialized WAL file to reach durable storage
       </entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+        <structfield>slot_sync_skip_count</structfield><type>bigint</type>
+       </para>
+       <para>
+        Number of times the slot sync is skipped.
+       </para>
+      </entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+        <structfield>last_slot_sync_skip</structfield><type>timestamp with time zone</type>
+       </para>
+       <para>
+        Time at which last slot sync was skipped.
+       </para>
+      </entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
         <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 4187191ea74..6b93c97ddd9 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -3036,6 +3036,14 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>slot_sync_skip_reason</structfield><type>text</type>
+      </para>
+      <para>
+       Reason of the last slot sync skip.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c77fa0234bb..abca2f5f927 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1046,7 +1046,8 @@ CREATE VIEW pg_replication_slots AS
             L.conflicting,
             L.invalidation_reason,
             L.failover,
-            L.synced
+            L.synced,
+            L.slot_sync_skip_reason
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1061,6 +1062,8 @@ CREATE VIEW pg_stat_replication_slots AS
             s.stream_bytes,
             s.total_txns,
             s.total_bytes,
+            s.slot_sync_skip_count,
+            s.last_slot_sync_skip,
             s.stats_reset
     FROM pg_replication_slots as r,
         LATERAL pg_stat_get_replication_slot(slot_name) as s
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 8c061d55bdb..29fac502a8a 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -148,6 +148,23 @@ typedef struct RemoteSlot
 static void slotsync_failure_callback(int code, Datum arg);
 static void update_synced_slots_inactive_since(void);
 
+/* Update slot sync skip stats */
+static void
+update_slot_sync_skip_stats(ReplicationSlot *slot, SlotSyncSkipReason skip_reason)
+{
+	/*
+	 * Update the slot sync related stats in pg_stat_replication_slot when a
+	 * slot sync is skipped
+	 */
+	if (skip_reason != SLOT_SYNC_SKIP_NONE)
+		pgstat_report_replslot_sync_skip(slot);
+
+	/* Update the slot sync reason */
+	SpinLockAcquire(&slot->mutex);
+	slot->slot_sync_skip_reason = skip_reason;
+	SpinLockRelease(&slot->mutex);
+}
+
 /*
  * If necessary, update the local synced slot's metadata based on the data
  * from the remote slot.
@@ -165,7 +182,8 @@ static void update_synced_slots_inactive_since(void);
 static bool
 update_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 						 bool *found_consistent_snapshot,
-						 bool *remote_slot_precedes)
+						 bool *remote_slot_precedes,
+						 SlotSyncSkipReason * skip_reason)
 {
 	ReplicationSlot *slot = MyReplicationSlot;
 	bool		updated_xmin_or_lsn = false;
@@ -218,6 +236,8 @@ update_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid,
 						  LSN_FORMAT_ARGS(slot->data.restart_lsn),
 						  slot->data.catalog_xmin));
 
+		*skip_reason = SLOT_SYNC_SKIP_REMOTE_BEHIND;
+
 		if (remote_slot_precedes)
 			*remote_slot_precedes = true;
 
@@ -557,7 +577,8 @@ reserve_wal_for_local_slot(XLogRecPtr restart_lsn)
  * false.
  */
 static bool
-update_and_persist_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid)
+update_and_persist_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid,
+									 SlotSyncSkipReason * skip_reason)
 {
 	ReplicationSlot *slot = MyReplicationSlot;
 	bool		found_consistent_snapshot = false;
@@ -565,7 +586,7 @@ update_and_persist_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 
 	(void) update_local_synced_slot(remote_slot, remote_dbid,
 									&found_consistent_snapshot,
-									&remote_slot_precedes);
+									&remote_slot_precedes, skip_reason);
 
 	/*
 	 * Check if the primary server has caught up. Refer to the comment atop
@@ -595,6 +616,8 @@ update_and_persist_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 				errdetail("Synchronization could lead to data loss, because the standby could not build a consistent snapshot to decode WALs at LSN %X/%08X.",
 						  LSN_FORMAT_ARGS(slot->data.restart_lsn)));
 
+		*skip_reason = SLOT_SYNC_SKIP_NO_CONSISTENT_SNAPSHOT;
+
 		return false;
 	}
 
@@ -626,6 +649,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 	ReplicationSlot *slot;
 	XLogRecPtr	latestFlushPtr;
 	bool		slot_updated = false;
+	SlotSyncSkipReason skip_reason = SLOT_SYNC_SKIP_NONE;
 
 	/*
 	 * Make sure that concerned WAL is received and flushed before syncing
@@ -646,7 +670,11 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 					   remote_slot->name,
 					   LSN_FORMAT_ARGS(latestFlushPtr)));
 
-		return false;
+		/* If slot is present on the local, update the slot sync skip stats */
+		if ((slot = SearchNamedReplicationSlot(remote_slot->name, true)))
+			skip_reason = SLOT_SYNC_SKIP_STANDBY_BEHIND;
+		else
+			return false;
 	}
 
 	/* Search for the named slot */
@@ -658,6 +686,17 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 		synced = slot->data.synced;
 		SpinLockRelease(&slot->mutex);
 
+		/*
+		 * If standby is behind remote slot and the synced slot is present on
+		 * local.
+		 */
+		if (remote_slot->confirmed_lsn > latestFlushPtr)
+		{
+			if (synced)
+				update_slot_sync_skip_stats(slot, skip_reason);
+			return false;
+		}
+
 		/* User-created slot with the same name exists, raise ERROR. */
 		if (!synced)
 			ereport(ERROR,
@@ -715,7 +754,8 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 		if (slot->data.persistency == RS_TEMPORARY)
 		{
 			slot_updated = update_and_persist_local_synced_slot(remote_slot,
-																remote_dbid);
+																remote_dbid,
+																&skip_reason);
 		}
 
 		/* Slot ready for sync, so sync it. */
@@ -737,7 +777,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 										   LSN_FORMAT_ARGS(remote_slot->confirmed_lsn)));
 
 			slot_updated = update_local_synced_slot(remote_slot, remote_dbid,
-													NULL, NULL);
+													NULL, NULL, &skip_reason);
 		}
 	}
 	/* Otherwise create the slot first. */
@@ -784,11 +824,18 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 		ReplicationSlotsComputeRequiredXmin(true);
 		LWLockRelease(ProcArrayLock);
 
-		update_and_persist_local_synced_slot(remote_slot, remote_dbid);
+		update_and_persist_local_synced_slot(remote_slot, remote_dbid,
+											 &skip_reason);
 
 		slot_updated = true;
 	}
 
+	/*
+	 * If slot sync is skipped update the reason and stats. Else reset the
+	 * reason to 'none' on successful slot sync.
+	 */
+	update_slot_sync_skip_stats(slot, skip_reason);
+
 	ReplicationSlotRelease();
 
 	return slot_updated;
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index b8f21153e7b..0033c03286f 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -228,6 +228,28 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * Map a SlotSyncSkipReason enum to a human-readable string
+ */
+static char *
+GetSlotSyncSkipReason(SlotSyncSkipReason reason)
+{
+	switch (reason)
+	{
+		case SLOT_SYNC_SKIP_NONE:
+			return pstrdup("none");
+		case SLOT_SYNC_SKIP_REMOTE_BEHIND:
+			return pstrdup("remote_behind");
+		case SLOT_SYNC_SKIP_STANDBY_BEHIND:
+			return pstrdup("standby_behind");
+		case SLOT_SYNC_SKIP_NO_CONSISTENT_SNAPSHOT:
+			return pstrdup("no_consistent_snapshot");
+	}
+
+	Assert(false);
+	return pstrdup("none");
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -235,7 +257,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 20
+#define PG_GET_REPLICATION_SLOTS_COLS 21
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -443,6 +465,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.synced);
 
+		values[i++] = CStringGetTextDatum(GetSlotSyncSkipReason(slot_contents.slot_sync_skip_reason));
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/utils/activity/pgstat_replslot.c b/src/backend/utils/activity/pgstat_replslot.c
index ccfb11c49bf..bf436472b8d 100644
--- a/src/backend/utils/activity/pgstat_replslot.c
+++ b/src/backend/utils/activity/pgstat_replslot.c
@@ -101,6 +101,31 @@ pgstat_report_replslot(ReplicationSlot *slot, const PgStat_StatReplSlotEntry *re
 	pgstat_unlock_entry(entry_ref);
 }
 
+/*
+ * Report replication slot sync skip statistics.
+ *
+ * We can rely on the stats for the slot to exist and to belong to this
+ * slot. We can only get here if pgstat_create_replslot() or
+ * pgstat_acquire_replslot() have already been called.
+ */
+void
+pgstat_report_replslot_sync_skip(ReplicationSlot *slot)
+{
+	PgStat_EntryRef *entry_ref;
+	PgStatShared_ReplSlot *shstatent;
+	PgStat_StatReplSlotEntry *statent;
+
+	entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_REPLSLOT, InvalidOid,
+											ReplicationSlotIndex(slot), false);
+	shstatent = (PgStatShared_ReplSlot *) entry_ref->shared_stats;
+	statent = &shstatent->stats;
+
+	statent->slot_sync_skip_count += 1;
+	statent->last_slot_sync_skip = GetCurrentTimestamp();
+
+	pgstat_unlock_entry(entry_ref);
+}
+
 /*
  * Report replication slot creation.
  *
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..15500a77701 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2100,7 +2100,7 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
 Datum
 pg_stat_get_replication_slot(PG_FUNCTION_ARGS)
 {
-#define PG_STAT_GET_REPLICATION_SLOT_COLS 10
+#define PG_STAT_GET_REPLICATION_SLOT_COLS 12
 	text	   *slotname_text = PG_GETARG_TEXT_P(0);
 	NameData	slotname;
 	TupleDesc	tupdesc;
@@ -2129,7 +2129,11 @@ pg_stat_get_replication_slot(PG_FUNCTION_ARGS)
 					   INT8OID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 9, "total_bytes",
 					   INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 10, "stats_reset",
+	TupleDescInitEntry(tupdesc, (AttrNumber) 10, "slot_sync_skip_count",
+					   INT8OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 11, "last_slot_sync_skip",
+					   TIMESTAMPTZOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 12, "stats_reset",
 					   TIMESTAMPTZOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
@@ -2154,11 +2158,17 @@ pg_stat_get_replication_slot(PG_FUNCTION_ARGS)
 	values[6] = Int64GetDatum(slotent->stream_bytes);
 	values[7] = Int64GetDatum(slotent->total_txns);
 	values[8] = Int64GetDatum(slotent->total_bytes);
+	values[9] = Int64GetDatum(slotent->slot_sync_skip_count);
+
+	if (slotent->last_slot_sync_skip == 0)
+		nulls[10] = true;
+	else
+		values[10] = TimestampTzGetDatum(slotent->last_slot_sync_skip);
 
 	if (slotent->stat_reset_timestamp == 0)
-		nulls[9] = true;
+		nulls[11] = true;
 	else
-		values[9] = TimestampTzGetDatum(slotent->stat_reset_timestamp);
+		values[11] = TimestampTzGetDatum(slotent->stat_reset_timestamp);
 
 	/* Returns the record as Datum */
 	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 03e82d28c87..d31a52b1fd4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5687,9 +5687,9 @@
 { oid => '6169', descr => 'statistics: information about replication slot',
   proname => 'pg_stat_get_replication_slot', provolatile => 's',
   proparallel => 'r', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,int8,int8,int8,int8,int8,int8,int8,timestamptz}',
-  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o}',
-  proargnames => '{slot_name,slot_name,spill_txns,spill_count,spill_bytes,stream_txns,stream_count,stream_bytes,total_txns,total_bytes,stats_reset}',
+  proallargtypes => '{text,text,int8,int8,int8,int8,int8,int8,int8,int8,int8,timestamptz,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{slot_name,slot_name,spill_txns,spill_count,spill_bytes,stream_txns,stream_count,stream_bytes,total_txns,total_bytes,slot_sync_skip_count,last_slot_sync_skip,stats_reset}',
   prosrc => 'pg_stat_get_replication_slot' },
 
 { oid => '6230', descr => 'statistics: check if a stats object exists',
@@ -11503,9 +11503,9 @@
   proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f',
   proretset => 't', provolatile => 's', prorettype => 'record',
   proargtypes => '',
-  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,pg_lsn,timestamptz,bool,text,bool,bool}',
-  proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
-  proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,safe_wal_size,two_phase,two_phase_at,inactive_since,conflicting,invalidation_reason,failover,synced}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,pg_lsn,timestamptz,bool,text,bool,bool,text}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,safe_wal_size,two_phase,two_phase_at,inactive_since,conflicting,invalidation_reason,failover,synced,slot_sync_skip_reason}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index f402b17295c..906d7b7799f 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -395,6 +395,8 @@ typedef struct PgStat_StatReplSlotEntry
 	PgStat_Counter stream_bytes;
 	PgStat_Counter total_txns;
 	PgStat_Counter total_bytes;
+	PgStat_Counter slot_sync_skip_count;
+	TimestampTz last_slot_sync_skip;
 	TimestampTz stat_reset_timestamp;
 } PgStat_StatReplSlotEntry;
 
@@ -736,6 +738,7 @@ extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id);
 extern void pgstat_reset_replslot(const char *name);
 struct ReplicationSlot;
 extern void pgstat_report_replslot(struct ReplicationSlot *slot, const PgStat_StatReplSlotEntry *repSlotStat);
+extern void pgstat_report_replslot_sync_skip(struct ReplicationSlot *slot);
 extern void pgstat_create_replslot(struct ReplicationSlot *slot);
 extern void pgstat_acquire_replslot(struct ReplicationSlot *slot);
 extern void pgstat_drop_replslot(struct ReplicationSlot *slot);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index fe62162cde3..09932747bd3 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -71,6 +71,20 @@ typedef enum ReplicationSlotInvalidationCause
 /* Maximum number of invalidation causes */
 #define	RS_INVAL_MAX_CAUSES 4
 
+/*
+ * When slot sync worker is running or pg_sync_replication_slots is run, the
+ * slot sync can be skipped. This enum keeps a list of reasons of slot sync
+ * skip.
+ */
+typedef enum SlotSyncSkipReason
+{
+	SLOT_SYNC_SKIP_NONE,		/* No skip */
+	SLOT_SYNC_SKIP_STANDBY_BEHIND,	/* Standby is behind the remote slot */
+	SLOT_SYNC_SKIP_REMOTE_BEHIND,	/* Remote slot is behind the local slot */
+	SLOT_SYNC_SKIP_NO_CONSISTENT_SNAPSHOT	/* Standby could not reach a
+											 * consistent snapshot */
+}			SlotSyncSkipReason;
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -249,6 +263,9 @@ typedef struct ReplicationSlot
 	 */
 	XLogRecPtr	last_saved_restart_lsn;
 
+	/* The reason for last slot sync skip */
+	SlotSyncSkipReason slot_sync_skip_reason;
+
 } ReplicationSlot;
 
 #define SlotIsPhysical(slot) ((slot)->data.database == InvalidOid)
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..041d3328328 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1499,8 +1499,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.conflicting,
     l.invalidation_reason,
     l.failover,
-    l.synced
-   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, safe_wal_size, two_phase, two_phase_at, inactive_since, conflicting, invalidation_reason, failover, synced)
+    l.synced,
+    l.slot_sync_skip_reason
+   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, safe_wal_size, two_phase, two_phase_at, inactive_since, conflicting, invalidation_reason, failover, synced, slot_sync_skip_reason)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
@@ -2140,9 +2141,11 @@ pg_stat_replication_slots| SELECT s.slot_name,
     s.stream_bytes,
     s.total_txns,
     s.total_bytes,
+    s.slot_sync_skip_count,
+    s.last_slot_sync_skip,
     s.stats_reset
    FROM pg_replication_slots r,
-    LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, stats_reset)
+    LATERAL pg_stat_get_replication_slot((r.slot_name)::text) s(slot_name, spill_txns, spill_count, spill_bytes, stream_txns, stream_count, stream_bytes, total_txns, total_bytes, slot_sync_skip_count, last_slot_sync_skip, stats_reset)
   WHERE (r.datoid IS NOT NULL);
 pg_stat_slru| SELECT name,
     blks_zeroed,
-- 
2.34.1

