public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH 5/7] fix
113+ messages / 10 participants
[nested] [flat]

* [PATCH 5/7] fix
@ 2020-03-19 01:54 Tomas Vondra <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Tomas Vondra @ 2020-03-19 01:54 UTC (permalink / raw)

---
 .../postgres_fdw/expected/postgres_fdw.out    |  4 ++--
 src/backend/executor/execExpr.c               |  5 +++--
 src/backend/executor/nodeAgg.c                | 20 +++++++++----------
 src/backend/optimizer/util/pathnode.c         |  4 ++--
 4 files changed, 17 insertions(+), 16 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 62c2697920..fc0ed2f4d5 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -3448,8 +3448,8 @@ select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls la
    Sort Key: ft1.c2
    ->  MixedAggregate
          Output: c2, sum(c1)
-         Hash Key: ft1.c2
          Group Key: ()
+         Hash Key: ft1.c2
          ->  Foreign Scan on public.ft1
                Output: c2, c1
                Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3))
@@ -3473,8 +3473,8 @@ select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last
    Sort Key: ft1.c2
    ->  MixedAggregate
          Output: c2, sum(c1)
-         Hash Key: ft1.c2
          Group Key: ()
+         Hash Key: ft1.c2
          ->  Foreign Scan on public.ft1
                Output: c2, c1
                Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3))
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 07789501f7..669843faf5 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2937,7 +2937,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, bool nullcheck)
 	PlanState  *parent = &aggstate->ss.ps;
 	ExprEvalStep scratch = {0};
 	bool		isCombine = DO_AGGSPLIT_COMBINE(aggstate->aggsplit);
-	ListCell	*lc;
+	ListCell   *lc;
 	LastAttnumInfo deform = {0, 0, 0};
 
 	state->expr = (Expr *) aggstate;
@@ -2978,6 +2978,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, bool nullcheck)
 		NullableDatum *strictargs = NULL;
 		bool	   *strictnulls = NULL;
 		int			argno;
+		int			setno;
 		ListCell   *bail;
 
 		/*
@@ -3155,7 +3156,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, bool nullcheck)
 		 * grouping set). Do so for both sort and hash based computations, as
 		 * applicable.
 		 */
-		for (int setno = 0; setno < phase->numsets; setno++)
+		for (setno = 0; setno < phase->numsets; setno++)
 		{
 			ExecBuildAggTransCall(state, aggstate, &scratch, trans_fcinfo,
 								  pertrans, transno, setno, phase, nullcheck);
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 20c5eb98b3..38d0bd5895 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -333,7 +333,7 @@ initialize_phase(AggState *aggstate, int newphase)
 	AggStatePerPhaseSort persort;
 
 	Assert(newphase == 0 || newphase == aggstate->current_phase + 1);
-	
+
 	/* Don't use aggstate->phase here, it might not be initialized yet*/
 	current_phase = aggstate->phases[aggstate->current_phase];
 
@@ -1516,7 +1516,7 @@ hash_agg_entry_size(int numAggs, Size tupleWidth, Size transitionSpace)
  * When called, CurrentMemoryContext should be the per-query context. The
  * already-calculated hash value for the tuple must be specified.
  */
-static void 
+static void
 lookup_hash_entry(AggState *aggstate, AggStatePerPhaseHash perhash, uint32 hash)
 {
 	TupleTableSlot *hashslot = perhash->hashslot;
@@ -1724,7 +1724,7 @@ agg_retrieve_direct(AggState *aggstate)
 					numGroupingSets = aggstate->phase->numsets;
 					node = aggstate->phase->aggnode;
 					numReset = numGroupingSets;
-					pergroups = aggstate->phase->pergroups; 
+					pergroups = aggstate->phase->pergroups;
 				}
 				else
 				{
@@ -2123,7 +2123,7 @@ agg_retrieve_hash_table(AggState *aggstate)
 				 */
 				select_current_set(aggstate, 0, true);
 				initialize_phase(aggstate, aggstate->current_phase + 1);
-				perhash = (AggStatePerPhaseHash) aggstate->phase;	
+				perhash = (AggStatePerPhaseHash) aggstate->phase;
 				ResetTupleHashIterator(perhash->hashtable, &perhash->hashiter);
 
 				continue;
@@ -2269,7 +2269,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 
 	aggstate->maxsets = numGroupingSets;
 	aggstate->numphases = 1 + list_length(node->chain);
-	
+
 	/*
 	 * The first phase is not sorted, agg need to do its own sort. See
 	 * agg_sort_input(), this can only happen in groupingsets case.
@@ -2390,7 +2390,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	numaggs = aggstate->numaggs;
 	Assert(numaggs == list_length(aggstate->aggs));
 
-	/* 
+	/*
 	 * For each phase, prepare grouping set data and fmgr lookup data for
 	 * compare functions.  Accumulate all_grouped_cols in passing.
 	 */
@@ -2430,7 +2430,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 
 			all_grouped_cols = bms_add_members(all_grouped_cols, cols);
 
-			/* 
+			/*
 			 * Initialize pergroup state. For AGG_HASHED, all groups do transition
 			 * on the fly, all pergroup states are kept in hashtable, everytime
 			 * a tuple is processed, lookup_hash_entry() choose one group and
@@ -2499,7 +2499,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 				phasedata->grouped_cols = NULL;
 			}
 
-			/* 
+			/*
 			 * Initialize pergroup states for AGG_SORTED/AGG_PLAIN/AGG_MIXED
 			 * phases, each set only have one group on the fly, all groups in
 			 * a set can reuse a pergroup state. Unlike AGG_HASHED, we
@@ -3610,8 +3610,8 @@ ExecReScanAgg(AggState *node)
 					   sizeof(AggStatePerGroupData) * node->numaggs);
 		}
 
-		/* 
-		 * the agg did its own first sort using tuplesort and the first
+		/*
+		 * The agg did its own first sort using tuplesort and the first
 		 * tuplesort is kept (see initialize_phase), if the subplan does
 		 * not have any parameter changes, and none of our own parameter
 		 * changes affect input expressions of the aggregated functions,
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 2dfa3fa17e..ff8f676dfb 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -2983,7 +2983,7 @@ create_agg_path(PlannerInfo *root,
  * 'rollups' is a list of RollupData nodes
  * 'agg_costs' contains cost info about the aggregate functions to be computed
  * 'numGroups' is the estimated total number of groups
- * 'is_sorted' is the input sorted in the group cols of first rollup 
+ * 'is_sorted' is the input sorted in the group cols of first rollup
  */
 GroupingSetsPath *
 create_groupingsets_path(PlannerInfo *root,
@@ -3098,7 +3098,7 @@ create_groupingsets_path(PlannerInfo *root,
 			AggStrategy	rollup_strategy;
 			Path	sort_path;	/* dummy for result of cost_sort */
 			Path	agg_path;	/* dummy for result of cost_agg */
-			
+
 			sort_path.startup_cost = 0;
 			sort_path.total_cost = 0;
 			sort_path.rows = subpath->rows;
-- 
2.21.1


--4ms6w442s2ji2wqe
Content-Type: text/plain; charset=iso-8859-1
Content-Disposition: attachment;
	filename="0006-Parallel-grouping-sets.patch"
Content-Transfer-Encoding: 8bit



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* [PATCH 7/7] fix
@ 2020-03-19 02:02 Tomas Vondra <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Tomas Vondra @ 2020-03-19 02:02 UTC (permalink / raw)

---
 src/backend/executor/nodeAgg.c       | 20 ++++++++++----------
 src/backend/jit/llvm/llvmjit_expr.c  |  2 +-
 src/backend/optimizer/plan/planner.c | 12 ++++++------
 3 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index f7b98dd798..51c7f229e2 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -360,7 +360,7 @@ initialize_phase(AggState *aggstate, int newphase)
 		if (persort->store_in)
 		{
 			tuplestore_end(persort->store_in);
-			persort->store_in = NULL;	
+			persort->store_in = NULL;
 		}
 	}
 
@@ -2017,7 +2017,7 @@ agg_preprocess_groupingsets(AggState *aggstate)
 	/* Initialize tuples storage for each aggregate phases */
 	for (phaseidx = 0; phaseidx < aggstate->numphases; phaseidx++)
 	{
-		phase = aggstate->phases[phaseidx];	
+		phase = aggstate->phases[phaseidx];
 
 		if (!phase->is_hashed)
 		{
@@ -2039,12 +2039,12 @@ agg_preprocess_groupingsets(AggState *aggstate)
 			}
 			else
 			{
-				persort->store_in = tuplestore_begin_heap(false, false, work_mem);	
+				persort->store_in = tuplestore_begin_heap(false, false, work_mem);
 			}
 		}
 		else
 		{
-			/* 
+			/*
 			 * If it's a AGG_HASHED, we don't need a storage to store
 			 * the tuples for later process, we can do the transition
 			 * immediately.
@@ -2422,7 +2422,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	aggstate->maxsets = numGroupingSets;
 	aggstate->numphases = 1 + list_length(node->chain);
 
-	/* 
+	/*
 	 * We are doing final stage of partial groupingsets, do preprocess
 	 * to input tuples first, redirect the tuples to according aggregate
 	 * phases. See agg_preprocess_groupingsets().
@@ -2431,7 +2431,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	{
 		aggstate->groupingsets_preprocess = true;
 
-		/* 
+		/*
 		 * Allocate gsetid <-> phases mapping, in final stage of
 		 * partial groupingsets, all grouping sets are extracted
 		 * to individual phases, so the number of sets is equal
@@ -2449,7 +2449,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	 * agg_sort_input(), this can only happen in groupingsets case.
 	 */
 	if (node->sortnode)
-		aggstate->input_sorted = false;	
+		aggstate->input_sorted = false;
 
 	aggstate->aggcontexts = (ExprContext **)
 		palloc0(sizeof(ExprContext *) * numGroupingSets);
@@ -2626,7 +2626,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 				phasedata->setno_gsetids[0] = gs->setId;
 			}
 
-			/* 
+			/*
 			 * Initialize pergroup state. For AGG_HASHED, all groups do transition
 			 * on the fly, all pergroup states are kept in hashtable, everytime
 			 * a tuple is processed, lookup_hash_entry() choose one group and
@@ -2688,7 +2688,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 					phasedata->grouped_cols[i] = cols;
 					phasedata->gset_lengths[i] = current_length;
 
-					/* 
+					/*
 					 * In the initial stage of partial grouping sets, it provides extra
 					 * grouping sets ID in the targetlist, fill the setno <-> gsetid
 					 * map, so EEOP_GROUPING_SET_ID can evaluate correct gsetid for
@@ -3871,7 +3871,7 @@ ExecReScanAgg(AggState *node)
 			}
 		}
 
-		/* 
+		/*
 		 * if the agg is doing final stage of partial groupingsets, reset the
 		 * flag to do groupingsets preprocess again.
 		 */
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index f442442269..f70eaabd0c 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -1893,7 +1893,7 @@ llvm_compile_expr(ExprState *state)
 					v_aggstatep =
 						LLVMBuildBitCast(b, v_parent, l_ptr(StructAggState), "");
 
-					/* 
+					/*
 					 * op->resvalue =
 					 * aggstate->phase->setno_gsetids
 					 * [aggstate->current_set]
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index cedd3e1c9d..a0186091a1 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -4211,7 +4211,7 @@ create_ordinary_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
  * added to grouped_rel->pathlist. And aggsplit value is not sufficient to
  * make a decision.
  */
-static void 
+static void
 consider_groupingsets_paths(PlannerInfo *root,
 							RelOptInfo *grouped_rel,
 							Path *path,
@@ -4601,7 +4601,7 @@ consider_groupingsets_paths(PlannerInfo *root,
 	}
 }
 
-/* 
+/*
  * If we are combining the partial groupingsets aggregation, the input is
  * mixed with tuples from different grouping sets, executor dispatch the
  * tuples to different rollups (phases) according to the grouping set id.
@@ -4644,7 +4644,7 @@ extract_final_rollups(PlannerInfo *root,
 			{
 				new_rollup->groupClause = NIL;
 				new_rollup->gsets_data = list_make1(gs);
-				new_rollup->gsets = list_make1(NIL); 
+				new_rollup->gsets = list_make1(NIL);
 				new_rollup->hashable = false;
 				new_rollup->is_hashed = false;
 			}
@@ -5364,7 +5364,7 @@ make_partial_grouping_target(PlannerInfo *root,
 
 	add_new_columns_to_pathtarget(partial_target, non_group_exprs);
 
-	/* 
+	/*
 	 * We are generate partial groupingsets path, add an expression to show
 	 * the grouping set ID for a tuple, so in the final stage, executor can
 	 * know which set this tuple belongs to and combine them.
@@ -6985,7 +6985,7 @@ create_partial_grouping_paths(PlannerInfo *root,
 													 path,
 													 root->group_pathkeys,
 													 -1.0);
-				
+
 				if (parse->hasAggs)
 					add_partial_path(partially_grouped_rel, (Path *)
 									 create_agg_path(root,
@@ -7059,7 +7059,7 @@ create_partial_grouping_paths(PlannerInfo *root,
 										AGGSPLIT_INITIAL_SERIAL,
 										add_partial_path);
 		}
-		else 
+		else
 		{
 			hashaggtablesize =
 				estimate_hashagg_tablesize(cheapest_partial_path,
-- 
2.21.1


--4ms6w442s2ji2wqe--





^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* [PATCH 4/8] fix
@ 2020-03-19 15:55 Tomas Vondra <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Tomas Vondra @ 2020-03-19 15:55 UTC (permalink / raw)

---
 src/backend/commands/explain.c       |  6 ++--
 src/backend/executor/execProcnode.c  | 11 ++++----
 src/backend/optimizer/plan/planner.c | 41 +++++++++++++++++-----------
 src/backend/utils/sort/tuplesort.c   | 10 +++++--
 src/include/nodes/plannodes.h        |  1 -
 5 files changed, 42 insertions(+), 27 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index dd4600a214..0256dd42f1 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2767,7 +2767,7 @@ show_incremental_sort_group_info(IncrementalSortGroupInfo *groupInfo,
 
 		foreach(methodCell, groupInfo->sortMethods)
 		{
-			const	   *sortMethodName = tuplesort_method_name(methodCell->int_value);
+			const char *sortMethodName = tuplesort_method_name(methodCell->int_value);
 
 			methodNames = lappend(methodNames, sortMethodName);
 		}
@@ -2776,7 +2776,7 @@ show_incremental_sort_group_info(IncrementalSortGroupInfo *groupInfo,
 		if (groupInfo->maxMemorySpaceUsed > 0)
 		{
 			long		avgSpace = groupInfo->totalMemorySpaceUsed / groupInfo->groupCount;
-			const	   *spaceTypeName;
+			const char *spaceTypeName;
 
 			ExplainPropertyInteger("Average Sort Space Used", "kB", avgSpace, es);
 			ExplainPropertyInteger("Maximum Sort Space Used", "kB",
@@ -2787,7 +2787,7 @@ show_incremental_sort_group_info(IncrementalSortGroupInfo *groupInfo,
 		if (groupInfo->maxDiskSpaceUsed > 0)
 		{
 			long		avgSpace = groupInfo->totalDiskSpaceUsed / groupInfo->groupCount;
-			const	   *spaceTypeName;
+			const char *spaceTypeName;
 
 			ExplainPropertyInteger("Average Sort Space Used", "kB", avgSpace, es);
 			ExplainPropertyInteger("Maximum Sort Space Used", "kB",
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index d15a86a706..5662e7d742 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -852,12 +852,13 @@ ExecSetTupleBound(int64 tuples_needed, PlanState *child_node)
 	else if (IsA(child_node, IncrementalSortState))
 	{
 		/*
-		 * If it is a Sort node, notify it that it can use bounded sort.
+		 * If it is an IncrementalSort node, notify it that it can use bounded
+		 * sort.
 		 *
-		 * Note: it is the responsibility of nodeSort.c to react properly to
-		 * changes of these parameters.  If we ever redesign this, it'd be a
-		 * good idea to integrate this signaling with the parameter-change
-		 * mechanism.
+		 * Note: it is the responsibility of nodeIncrementalSort.c to react
+		 * properly to changes of these parameters.  If we ever redesign this,
+		 * it'd be a good idea to integrate this signaling with the
+		 * parameter-change mechanism.
 		 */
 		IncrementalSortState *sortState = (IncrementalSortState *) child_node;
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b2f4aaadb5..0e01cf8cb1 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -4869,7 +4869,7 @@ create_distinct_paths(PlannerInfo *root,
 	else
 	{
 		Size		hashentrysize = hash_agg_entry_size(
-														0, cheapest_input_path->pathtarget->width, 0);
+			0, cheapest_input_path->pathtarget->width, 0);
 
 		/* Allow hashing only if hashtable is predicted to fit in work_mem */
 		allow_hash = (hashentrysize * numDistinctRows <= work_mem * 1024L);
@@ -4932,6 +4932,9 @@ create_distinct_paths(PlannerInfo *root,
  * target: the output tlist the result Paths must emit
  * limit_tuples: estimated bound on the number of output tuples,
  *		or -1 if no LIMIT or couldn't estimate
+ *
+ * XXX This only looks at sort_pathkeys. I wonder if it needs to look at the
+ * other pathkeys (grouping, ...) like generate_useful_gather_paths.
  */
 static RelOptInfo *
 create_ordered_paths(PlannerInfo *root,
@@ -5002,23 +5005,29 @@ create_ordered_paths(PlannerInfo *root,
 
 				add_path(ordered_rel, sorted_path);
 			}
-			if (enable_incrementalsort && presorted_keys > 0)
-			{
-				/* Also consider incremental sort. */
-				sorted_path = (Path *) create_incremental_sort_path(root,
-																	ordered_rel,
-																	input_path,
-																	root->sort_pathkeys,
-																	presorted_keys,
-																	limit_tuples);
 
-				/* Add projection step if needed */
-				if (sorted_path->pathtarget != target)
-					sorted_path = apply_projection_to_path(root, ordered_rel,
-														   sorted_path, target);
+			/* With incremental sort disabled, don't build those paths. */
+			if (!enable_incrementalsort)
+				continue;
 
-				add_path(ordered_rel, sorted_path);
-			}
+			/* Likewise, if the path can't be used for incremental sort. */
+			if (!presorted_keys)
+				continue;
+
+			/* Also consider incremental sort. */
+			sorted_path = (Path *) create_incremental_sort_path(root,
+																ordered_rel,
+																input_path,
+																root->sort_pathkeys,
+																presorted_keys,
+																limit_tuples);
+
+			/* Add projection step if needed */
+			if (sorted_path->pathtarget != target)
+				sorted_path = apply_projection_to_path(root, ordered_rel,
+													   sorted_path, target);
+
+			add_path(ordered_rel, sorted_path);
 		}
 	}
 
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 583551d197..77c15ebd78 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -256,8 +256,8 @@ struct Tuplesortstate
 	bool		isMaxSpaceDisk; /* true when maxSpace is value for on-disk
 								 * space, false when it's value for in-memory
 								 * space */
-	TupSortStatus maxSpaceStatus;	/* sort status when maxSpace was reached */
-	MemoryContext maincontext;	/* memory context for tuple sort metadata that
+	TupSortStatus	maxSpaceStatus;	/* sort status when maxSpace was reached */
+	MemoryContext	maincontext;	/* memory context for tuple sort metadata that
 								 * persists across multiple batches */
 	MemoryContext sortcontext;	/* memory context holding most sort data */
 	MemoryContext tuplecontext; /* sub-context of sortcontext for tuple data */
@@ -803,6 +803,9 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate,
 	return state;
 }
 
+/*
+ * XXX Missing comment.
+ */
 static void
 tuplesort_begin_batch(Tuplesortstate *state)
 {
@@ -1288,6 +1291,9 @@ tuplesort_set_bound(Tuplesortstate *state, int64 bound)
 	state->sortKeys->abbrev_full_comparator = NULL;
 }
 
+/*
+ * XXX Missing comment.
+ */
 bool
 tuplesort_used_bound(Tuplesortstate *state)
 {
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fe4046b64b..136d794219 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -774,7 +774,6 @@ typedef struct Sort
 	bool	   *nullsFirst;		/* NULLS FIRST/LAST directions */
 } Sort;
 
-
 /* ----------------
  *		incremental sort node
  * ----------------
-- 
2.21.1


--dfcjsgdukgytabqd
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment;
	filename*0=v39-0005-Consider-incremental-sort-paths-in-additional-places;
	filename*1=".patch"



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* [PATCH 6/8] fix
@ 2020-03-19 17:26 Tomas Vondra <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Tomas Vondra @ 2020-03-19 17:26 UTC (permalink / raw)

---
 src/backend/optimizer/path/allpaths.c | 62 +++++++++++++++++----------
 1 file changed, 39 insertions(+), 23 deletions(-)

diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 9a92948fe3..6838a238cd 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -2764,6 +2764,14 @@ find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)
  * order matches the final output ordering for the overall query we're
  * planning, or because it enables an efficient merge join.  Here, we try
  * to figure out which pathkeys to consider.
+ *
+ * This allows us to do incremental sort on top of an index scan under a gather
+ * merge node, i.e. parallelized.
+ *
+ * XXX At the moment this can only ever return a list with a single element,
+ * because it looks at query_pathkeys only. So we might return the pathkeys
+ * directly, but it seems plausible we'll want to consider other orderings
+ * in the future.
  */
 static List *
 get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
@@ -2772,8 +2780,8 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
 	ListCell   *lc;
 
 	/*
-	 * Pushing the query_pathkeys to the remote server is always worth
-	 * considering, because it might let us avoid a local sort.
+	 * Considering query_pathkeys is always worth it, because it might let us
+	 * avoid a local sort.
 	 */
 	if (root->query_pathkeys)
 	{
@@ -2786,14 +2794,9 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
 			Expr	   *em_expr;
 
 			/*
-			 * The planner and executor don't have any clever strategy for
-			 * taking data sorted by a prefix of the query's pathkeys and
-			 * getting it to be sorted by all of those pathkeys. We'll just
-			 * end up re-sorting the entire data set.  So, unless we can push
-			 * down all of the query pathkeys, forget it.
-			 *
-			 * is_foreign_expr would detect volatile expressions as well, but
-			 * checking ec_has_volatile here saves some cycles.
+			 * We can't use incremental sort for pathkeys containing volatile
+			 * expressions. We could walk the exppression itself, but checking
+			 * ec_has_volatile here saves some cycles.
 			 */
 			if (pathkey_ec->ec_has_volatile ||
 				!(em_expr = find_em_expr_for_rel(pathkey_ec, rel)))
@@ -2803,10 +2806,6 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
 			}
 		}
 
-		/*
-		 * This ends up allowing us to do incremental sort on top of an index
-		 * scan all parallelized under a gather merge node.
-		 */
 		if (query_pathkeys_ok)
 			useful_pathkeys_list = list_make1(list_copy(root->query_pathkeys));
 	}
@@ -2819,10 +2818,10 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
  *		Generate parallel access paths for a relation by pushing a Gather or
  *		Gather Merge on top of a partial path.
  *
- * Unlike generate_gather_paths, this does not look only at pathkeys of the
- * input paths (aiming to preserve the ordering). It also considers ordering
- * that might be useful by nodes above the gather merge node, and tries to
- * add a sort (regular or incremental) to provide that.
+ * Unlike plain generate_gather_paths, this looks both at pathkeys of input
+ * paths (aiming to preserve the ordering), but also considers ordering that
+ * might be useful for nodes above the gather merge node, and tries to add
+ * a sort (regular or incremental) to provide that.
  */
 void
 generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows)
@@ -2841,7 +2840,7 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r
 	if (override_rows)
 		rowsp = &rows;
 
-	/* generate the regular gather merge paths */
+	/* generate the regular gather (merge) paths */
 	generate_gather_paths(root, rel, override_rows);
 
 	/* when incremental sort is disabled, we're done */
@@ -2851,7 +2850,7 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r
 	/* consider incremental sort for interesting orderings */
 	useful_pathkeys_list = get_useful_pathkeys_for_relation(root, rel);
 
-	/* used for explicit sort paths */
+	/* used for explicit (full) sort paths */
 	cheapest_partial_path = linitial(rel->partial_pathlist);
 
 	/*
@@ -2880,6 +2879,14 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r
 													 subpath->pathkeys,
 													 &presorted_keys);
 
+			/*
+			 * When the partial path is already sorted, we can just add a gather
+			 * merge on top, and we're done - no point in adding explicit sort.
+			 *
+			 * XXX Can't we skip this (maybe only for the cheapest partial path)
+			 * when the path is already sorted? Then it's likely duplicate with
+			 * the path created by generate_gather_paths.
+			 */
 			if (is_sorted)
 			{
 				path = create_gather_merge_path(root, rel, subpath, rel->reltarget,
@@ -2892,8 +2899,14 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r
 			Assert(!is_sorted);
 
 			/*
-			 * consider regular sort for cheapest partial path (for each
-			 * useful pathkeys)
+			 * Consider regular sort for the cheapest partial path (for each
+			 * useful pathkeys). We know the path is not sorted, because we'd
+			 * not get here otherwise.
+			 *
+			 * XXX This is not redundant with the gather merge path created in
+			 * generate_gather_paths, because that merely preserves ordering of
+			 * the cheapest partial path, while here we add an explicit sort to
+			 * get match the useful ordering.
 			 */
 			if (cheapest_partial_path == subpath)
 			{
@@ -2919,7 +2932,10 @@ generate_useful_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_r
 				/* Fall through */
 			}
 
-			/* Also consider incremental sort */
+			/*
+			 * Consider incremental sort, but only when the subpath is already
+			 * partially sorted on a pathkey prefix.
+			 */
 			if (presorted_keys > 0)
 			{
 				Path	   *tmp;
-- 
2.21.1


--dfcjsgdukgytabqd
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment;
	filename="v39-0007-A-couple-more-places-for-incremental-sort.patch"



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* [PATCH 8/8] fix
@ 2020-03-19 17:48 Tomas Vondra <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Tomas Vondra @ 2020-03-19 17:48 UTC (permalink / raw)

---
 src/backend/optimizer/plan/planner.c | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 2880fcabe8..b4763e79f8 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5100,17 +5100,17 @@ create_ordered_paths(PlannerInfo *root,
 				double		total_groups;
 
 				/*
-				 * We don't care if this is the cheapest partial path - we
-				 * can't simply skip it, because it may be partially sorted in
-				 * which case we want to consider incremental sort on top of
-				 * it (instead of full sort, which is what happens above).
+				 * We don't care if this is the cheapest partial path - we can't
+				 * simply skip it, because it may be partially sorted in which
+				 * case we want to consider adding incremental sort (instead of
+				 * full sort, which is what happens above).
 				 */
 
 				is_sorted = pathkeys_common_contained_in(root->sort_pathkeys,
 														 input_path->pathkeys,
 														 &presorted_keys);
 
-				/* Ignore already sorted paths */
+				/* No point in adding incremental sort on fully sorted paths. */
 				if (is_sorted)
 					continue;
 
@@ -7005,9 +7005,7 @@ create_partial_grouping_paths(PlannerInfo *root,
 			}
 		}
 
-		/*
-		 * Also consider incremental sort on all partially sorted paths.
-		 */
+		/* Consider incremental sort on all partial paths, if enabled. */
 		if (enable_incrementalsort)
 		{
 			foreach(lc, input_rel->pathlist)
@@ -7122,10 +7120,10 @@ create_partial_grouping_paths(PlannerInfo *root,
 			/* We've already skipped fully sorted paths above. */
 			Assert(!is_sorted);
 
+			/* no shared prefix, not point in building incremental sort */
 			if (presorted_keys == 0)
 				continue;
 
-			/* Since we have presorted keys, consider incremental sort. */
 			path = (Path *) create_incremental_sort_path(root,
 														 partially_grouped_rel,
 														 path,
-- 
2.21.1


--dfcjsgdukgytabqd--





^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Allow tailoring of ICU locales with custom rules
@ 2022-12-14 09:26 Peter Eisentraut <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Peter Eisentraut @ 2022-12-14 09:26 UTC (permalink / raw)
  To: pgsql-hackers

This patch exposes the ICU facility to add custom collation rules to a 
standard collation.  This would allow users to customize any ICU 
collation to whatever they want.  A very simple example from the 
documentation/tests:

CREATE COLLATION en_custom
     (provider = icu, locale = 'en', rules = '&a < g');

This places "g" after "a" before "b".  Details about the syntax can be 
found at 
<https://unicode-org.github.io/icu/userguide/collation/customization/;.

The code is pretty straightforward.  It mainly just records these rules 
in the catalog and feeds them to ICU when creating the collator object.
From b0d42407a60e116d3ccb0ed04505aa362f8a6a1d Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Wed, 14 Dec 2022 10:15:03 +0100
Subject: [PATCH] Allow tailoring of ICU locales with custom rules

This exposes the ICU facility to add custom collation rules to a
standard collation.
---
 doc/src/sgml/catalogs.sgml                    | 18 +++++++
 doc/src/sgml/ref/create_collation.sgml        | 22 +++++++++
 doc/src/sgml/ref/create_database.sgml         | 12 +++++
 src/backend/catalog/pg_collation.c            |  5 ++
 src/backend/commands/collationcmds.c          | 21 ++++++--
 src/backend/commands/dbcommands.c             | 49 +++++++++++++++++--
 src/backend/utils/adt/pg_locale.c             | 41 +++++++++++++++-
 src/backend/utils/init/postinit.c             | 11 ++++-
 src/include/catalog/pg_collation.h            |  2 +
 src/include/catalog/pg_database.h             |  3 ++
 src/include/utils/pg_locale.h                 |  1 +
 .../regress/expected/collate.icu.utf8.out     | 30 ++++++++++++
 src/test/regress/sql/collate.icu.utf8.sql     | 13 +++++
 13 files changed, 219 insertions(+), 9 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9316b811ac..afa9f28ef9 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2428,6 +2428,15 @@ <title><structname>pg_collation</structname> Columns</title>
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>collicurules</structfield> <type>text</type>
+      </para>
+      <para>
+       ICU collation rules for this collation object
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>collversion</structfield> <type>text</type>
@@ -3106,6 +3115,15 @@ <title><structname>pg_database</structname> Columns</title>
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>daticurules</structfield> <type>text</type>
+      </para>
+      <para>
+       ICU collation rules for this database
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>datcollversion</structfield> <type>text</type>
diff --git a/doc/src/sgml/ref/create_collation.sgml b/doc/src/sgml/ref/create_collation.sgml
index 58f5f0cd63..2c7266107e 100644
--- a/doc/src/sgml/ref/create_collation.sgml
+++ b/doc/src/sgml/ref/create_collation.sgml
@@ -27,6 +27,7 @@
     [ LC_CTYPE = <replaceable>lc_ctype</replaceable>, ]
     [ PROVIDER = <replaceable>provider</replaceable>, ]
     [ DETERMINISTIC = <replaceable>boolean</replaceable>, ]
+    [ RULES = <replaceable>rules</replaceable>, ]
     [ VERSION = <replaceable>version</replaceable> ]
 )
 CREATE COLLATION [ IF NOT EXISTS ] <replaceable>name</replaceable> FROM <replaceable>existing_collation</replaceable>
@@ -149,6 +150,19 @@ <title>Parameters</title>
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><replaceable>rules</replaceable></term>
+
+     <listitem>
+      <para>
+       Specifies additional collation rules to customize the behavior of the
+       collation.  This is supported for ICU only.  See <ulink
+       url="https://unicode-org.github.io/icu/userguide/collation/customization/"/;
+       for details on the syntax.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><replaceable>version</replaceable></term>
 
@@ -228,6 +242,14 @@ <title>Examples</title>
 </programlisting>
   </para>
 
+  <para>
+   To create a collation using the ICU provider, based on the English ICU
+   locale, with custom rules:
+<programlisting>
+<![CDATA[CREATE COLLATION en_custom (provider = icu, locale = 'en', rules = '&a < g');]]>
+</programlisting>
+  </para>
+
   <para>
    To create a collation from an existing collation:
 <programlisting>
diff --git a/doc/src/sgml/ref/create_database.sgml b/doc/src/sgml/ref/create_database.sgml
index ea38c64731..aa6f121a81 100644
--- a/doc/src/sgml/ref/create_database.sgml
+++ b/doc/src/sgml/ref/create_database.sgml
@@ -192,6 +192,18 @@ <title>Parameters</title>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="parameter">icu_rules</replaceable></term>
+      <listitem>
+       <para>
+        Specifies additional collation rules to customize the behavior of the
+        collation.  This is supported for ICU only.  See <ulink
+        url="https://unicode-org.github.io/icu/userguide/collation/customization/"/;
+        for details on the syntax.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable>locale_provider</replaceable></term>
 
diff --git a/src/backend/catalog/pg_collation.c b/src/backend/catalog/pg_collation.c
index aa8fbe1a98..7ed9de3891 100644
--- a/src/backend/catalog/pg_collation.c
+++ b/src/backend/catalog/pg_collation.c
@@ -50,6 +50,7 @@ CollationCreate(const char *collname, Oid collnamespace,
 				int32 collencoding,
 				const char *collcollate, const char *collctype,
 				const char *colliculocale,
+				const char *collicurules,
 				const char *collversion,
 				bool if_not_exists,
 				bool quiet)
@@ -194,6 +195,10 @@ CollationCreate(const char *collname, Oid collnamespace,
 		values[Anum_pg_collation_colliculocale - 1] = CStringGetTextDatum(colliculocale);
 	else
 		nulls[Anum_pg_collation_colliculocale - 1] = true;
+	if (collicurules)
+		values[Anum_pg_collation_collicurules - 1] = CStringGetTextDatum(collicurules);
+	else
+		nulls[Anum_pg_collation_collicurules - 1] = true;
 	if (collversion)
 		values[Anum_pg_collation_collversion - 1] = CStringGetTextDatum(collversion);
 	else
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index 81e54e0ce6..50f16f2764 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -64,10 +64,12 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 	DefElem    *lcctypeEl = NULL;
 	DefElem    *providerEl = NULL;
 	DefElem    *deterministicEl = NULL;
+	DefElem    *rulesEl = NULL;
 	DefElem    *versionEl = NULL;
 	char	   *collcollate;
 	char	   *collctype;
 	char	   *colliculocale;
+	char	   *collicurules;
 	bool		collisdeterministic;
 	int			collencoding;
 	char		collprovider;
@@ -99,6 +101,8 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 			defelp = &providerEl;
 		else if (strcmp(defel->defname, "deterministic") == 0)
 			defelp = &deterministicEl;
+		else if (strcmp(defel->defname, "rules") == 0)
+			defelp = &rulesEl;
 		else if (strcmp(defel->defname, "version") == 0)
 			defelp = &versionEl;
 		else
@@ -161,6 +165,12 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 		else
 			colliculocale = NULL;
 
+		datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collicurules, &isnull);
+		if (!isnull)
+			collicurules = TextDatumGetCString(datum);
+		else
+			collicurules = NULL;
+
 		ReleaseSysCache(tp);
 
 		/*
@@ -182,6 +192,7 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 		collcollate = NULL;
 		collctype = NULL;
 		colliculocale = NULL;
+		collicurules = NULL;
 
 		if (providerEl)
 			collproviderstr = defGetString(providerEl);
@@ -191,6 +202,9 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 		else
 			collisdeterministic = true;
 
+		if (rulesEl)
+			collicurules = defGetString(rulesEl);
+
 		if (versionEl)
 			collversion = defGetString(versionEl);
 
@@ -297,6 +311,7 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 							 collcollate,
 							 collctype,
 							 colliculocale,
+							 collicurules,
 							 collversion,
 							 if_not_exists,
 							 false);	/* not quiet */
@@ -710,7 +725,7 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
 			 */
 			collid = CollationCreate(localebuf, nspid, GetUserId(),
 									 COLLPROVIDER_LIBC, true, enc,
-									 localebuf, localebuf, NULL,
+									 localebuf, localebuf, NULL, NULL,
 									 get_collation_actual_version(COLLPROVIDER_LIBC, localebuf),
 									 true, true);
 			if (OidIsValid(collid))
@@ -777,7 +792,7 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
 
 			collid = CollationCreate(alias, nspid, GetUserId(),
 									 COLLPROVIDER_LIBC, true, enc,
-									 locale, locale, NULL,
+									 locale, locale, NULL, NULL,
 									 get_collation_actual_version(COLLPROVIDER_LIBC, locale),
 									 true, true);
 			if (OidIsValid(collid))
@@ -839,7 +854,7 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
 			collid = CollationCreate(psprintf("%s-x-icu", langtag),
 									 nspid, GetUserId(),
 									 COLLPROVIDER_ICU, true, -1,
-									 NULL, NULL, iculocstr,
+									 NULL, NULL, iculocstr, NULL,
 									 get_collation_actual_version(COLLPROVIDER_ICU, iculocstr),
 									 true, true);
 			if (OidIsValid(collid))
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 6eb8742718..65d6ad46ef 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -119,6 +119,7 @@ static bool get_db_info(const char *name, LOCKMODE lockmode,
 						int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
 						TransactionId *dbFrozenXidP, MultiXactId *dbMinMultiP,
 						Oid *dbTablespace, char **dbCollate, char **dbCtype, char **dbIculocale,
+						char **dbIcurules,
 						char *dbLocProvider,
 						char **dbCollversion);
 static bool have_createdb_privilege(void);
@@ -676,6 +677,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 	char	   *src_collate = NULL;
 	char	   *src_ctype = NULL;
 	char	   *src_iculocale = NULL;
+	char	   *src_icurules = NULL;
 	char		src_locprovider = '\0';
 	char	   *src_collversion = NULL;
 	bool		src_istemplate;
@@ -699,6 +701,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 	DefElem    *dcollate = NULL;
 	DefElem    *dctype = NULL;
 	DefElem    *diculocale = NULL;
+	DefElem    *dicurules = NULL;
 	DefElem    *dlocprovider = NULL;
 	DefElem    *distemplate = NULL;
 	DefElem    *dallowconnections = NULL;
@@ -711,6 +714,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 	char	   *dbcollate = NULL;
 	char	   *dbctype = NULL;
 	char	   *dbiculocale = NULL;
+	char	   *dbicurules = NULL;
 	char		dblocprovider = '\0';
 	char	   *canonname;
 	int			encoding = -1;
@@ -776,6 +780,12 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 				errorConflictingDefElem(defel, pstate);
 			diculocale = defel;
 		}
+		else if (strcmp(defel->defname, "icu_rules") == 0)
+		{
+			if (dicurules)
+				errorConflictingDefElem(defel, pstate);
+			dicurules = defel;
+		}
 		else if (strcmp(defel->defname, "locale_provider") == 0)
 		{
 			if (dlocprovider)
@@ -959,7 +969,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 					 &src_dboid, &src_owner, &src_encoding,
 					 &src_istemplate, &src_allowconn,
 					 &src_frozenxid, &src_minmxid, &src_deftablespace,
-					 &src_collate, &src_ctype, &src_iculocale, &src_locprovider,
+					 &src_collate, &src_ctype, &src_iculocale, &src_icurules, &src_locprovider,
 					 &src_collversion))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_DATABASE),
@@ -1007,6 +1017,8 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 		dblocprovider = src_locprovider;
 	if (dbiculocale == NULL && dblocprovider == COLLPROVIDER_ICU)
 		dbiculocale = src_iculocale;
+	if (dbicurules == NULL && dblocprovider == COLLPROVIDER_ICU)
+		dbicurules = src_icurules;
 
 	/* Some encodings are client only */
 	if (!PG_VALID_BE_ENCODING(encoding))
@@ -1098,6 +1110,9 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 
 		if (dblocprovider == COLLPROVIDER_ICU)
 		{
+			char	   *val1;
+			char	   *val2;
+
 			Assert(dbiculocale);
 			Assert(src_iculocale);
 			if (strcmp(dbiculocale, src_iculocale) != 0)
@@ -1106,6 +1121,19 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 						 errmsg("new ICU locale (%s) is incompatible with the ICU locale of the template database (%s)",
 								dbiculocale, src_iculocale),
 						 errhint("Use the same ICU locale as in the template database, or use template0 as template.")));
+
+			val1 = dbicurules;
+			if (!val1)
+				val1 = "";
+			val2 = src_icurules;
+			if (!val2)
+				val2 = "";
+			if (strcmp(val1, val2) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("new ICU collation rules (%s) are incompatible with the ICU collation rules of the template database (%s)",
+								val1, val2),
+						 errhint("Use the same ICU collation rules as in the template database, or use template0 as template.")));
 		}
 	}
 
@@ -1314,6 +1342,10 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 		new_record[Anum_pg_database_daticulocale - 1] = CStringGetTextDatum(dbiculocale);
 	else
 		new_record_nulls[Anum_pg_database_daticulocale - 1] = true;
+	if (dbicurules)
+		new_record[Anum_pg_database_daticurules - 1] = CStringGetTextDatum(dbicurules);
+	else
+		new_record_nulls[Anum_pg_database_daticurules - 1] = true;
 	if (dbcollversion)
 		new_record[Anum_pg_database_datcollversion - 1] = CStringGetTextDatum(dbcollversion);
 	else
@@ -1527,7 +1559,7 @@ dropdb(const char *dbname, bool missing_ok, bool force)
 	pgdbrel = table_open(DatabaseRelationId, RowExclusiveLock);
 
 	if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL,
-					 &db_istemplate, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
+					 &db_istemplate, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
 	{
 		if (!missing_ok)
 		{
@@ -1727,7 +1759,7 @@ RenameDatabase(const char *oldname, const char *newname)
 	rel = table_open(DatabaseRelationId, RowExclusiveLock);
 
 	if (!get_db_info(oldname, AccessExclusiveLock, &db_id, NULL, NULL,
-					 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
+					 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_DATABASE),
 				 errmsg("database \"%s\" does not exist", oldname)));
@@ -1837,7 +1869,7 @@ movedb(const char *dbname, const char *tblspcname)
 	pgdbrel = table_open(DatabaseRelationId, RowExclusiveLock);
 
 	if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL,
-					 NULL, NULL, NULL, NULL, &src_tblspcoid, NULL, NULL, NULL, NULL, NULL))
+					 NULL, NULL, NULL, NULL, &src_tblspcoid, NULL, NULL, NULL, NULL, NULL, NULL))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_DATABASE),
 				 errmsg("database \"%s\" does not exist", dbname)));
@@ -2600,6 +2632,7 @@ get_db_info(const char *name, LOCKMODE lockmode,
 			int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
 			TransactionId *dbFrozenXidP, MultiXactId *dbMinMultiP,
 			Oid *dbTablespace, char **dbCollate, char **dbCtype, char **dbIculocale,
+			char **dbIcurules,
 			char *dbLocProvider,
 			char **dbCollversion)
 {
@@ -2716,6 +2749,14 @@ get_db_info(const char *name, LOCKMODE lockmode,
 					else
 						*dbIculocale = TextDatumGetCString(datum);
 				}
+				if (dbIcurules)
+				{
+					datum = SysCacheGetAttr(DATABASEOID, tuple, Anum_pg_database_daticurules, &isnull);
+					if (isnull)
+						*dbIcurules = NULL;
+					else
+						*dbIcurules = TextDatumGetCString(datum);
+				}
 				if (dbCollversion)
 				{
 					datum = SysCacheGetAttr(DATABASEOID, tuple, Anum_pg_database_datcollversion, &isnull);
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 2b42d9ccd8..191d2e8a68 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -69,6 +69,7 @@
 
 #ifdef USE_ICU
 #include <unicode/ucnv.h>
+#include <unicode/ustring.h>
 #endif
 
 #ifdef __GLIBC__
@@ -1402,6 +1403,7 @@ struct pg_locale_struct default_locale;
 
 void
 make_icu_collator(const char *iculocstr,
+				  const char *icurules,
 				  struct pg_locale_struct *resultp)
 {
 #ifdef USE_ICU
@@ -1418,6 +1420,35 @@ make_icu_collator(const char *iculocstr,
 	if (U_ICU_VERSION_MAJOR_NUM < 54)
 		icu_set_collation_attributes(collator, iculocstr);
 
+	/*
+	 * If rules are specified, we extract the rules of the standard collation,
+	 * add our own rules, and make a new collator with the combined rules.
+	 */
+	if (icurules)
+	{
+		const UChar *default_rules;
+		UChar	   *agg_rules;
+		UChar	   *my_rules;
+		int32_t		length;
+
+		default_rules = ucol_getRules(collator, &length);
+		icu_to_uchar(&my_rules, icurules, strlen(icurules));
+
+		agg_rules = palloc_array(UChar, u_strlen(default_rules) + u_strlen(my_rules) + 1);
+		u_strcpy(agg_rules, default_rules);
+		u_strcat(agg_rules, my_rules);
+
+		ucol_close(collator);
+
+		status = U_ZERO_ERROR;
+		collator = ucol_openRules(agg_rules, u_strlen(agg_rules),
+								  UCOL_DEFAULT, UCOL_DEFAULT_STRENGTH, NULL, &status);
+		if (U_FAILURE(status))
+			ereport(ERROR,
+					(errmsg("could not open collator for locale \"%s\" with rules \"%s\": %s",
+							iculocstr, icurules, u_errorName(status))));
+	}
+
 	/* We will leak this string if the caller errors later :-( */
 	resultp->info.icu.locale = MemoryContextStrdup(TopMemoryContext, iculocstr);
 	resultp->info.icu.ucol = collator;
@@ -1580,11 +1611,19 @@ pg_newlocale_from_collation(Oid collid)
 		else if (collform->collprovider == COLLPROVIDER_ICU)
 		{
 			const char *iculocstr;
+			const char *icurules;
 
 			datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_colliculocale, &isnull);
 			Assert(!isnull);
 			iculocstr = TextDatumGetCString(datum);
-			make_icu_collator(iculocstr, &result);
+
+			datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collicurules, &isnull);
+			if (!isnull)
+				icurules = TextDatumGetCString(datum);
+			else
+				icurules = NULL;
+
+			make_icu_collator(iculocstr, icurules, &result);
 		}
 
 		datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collversion,
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index a990c833c5..55a035c062 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -421,10 +421,19 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
 
 	if (dbform->datlocprovider == COLLPROVIDER_ICU)
 	{
+		char	   *icurules;
+
 		datum = SysCacheGetAttr(DATABASEOID, tup, Anum_pg_database_daticulocale, &isnull);
 		Assert(!isnull);
 		iculocale = TextDatumGetCString(datum);
-		make_icu_collator(iculocale, &default_locale);
+
+		datum = SysCacheGetAttr(DATABASEOID, tup, Anum_pg_database_daticurules, &isnull);
+		if (!isnull)
+			icurules = TextDatumGetCString(datum);
+		else
+			icurules = NULL;
+
+		make_icu_collator(iculocale, icurules, &default_locale);
 	}
 	else
 		iculocale = NULL;
diff --git a/src/include/catalog/pg_collation.h b/src/include/catalog/pg_collation.h
index 2190ccb5b8..ad2d767d65 100644
--- a/src/include/catalog/pg_collation.h
+++ b/src/include/catalog/pg_collation.h
@@ -43,6 +43,7 @@ CATALOG(pg_collation,3456,CollationRelationId)
 	text		collcollate BKI_DEFAULT(_null_);	/* LC_COLLATE setting */
 	text		collctype BKI_DEFAULT(_null_);	/* LC_CTYPE setting */
 	text		colliculocale BKI_DEFAULT(_null_);	/* ICU locale ID */
+	text		collicurules BKI_DEFAULT(_null_);	/* ICU collation rules */
 	text		collversion BKI_DEFAULT(_null_);	/* provider-dependent
 													 * version of collation
 													 * data */
@@ -91,6 +92,7 @@ extern Oid	CollationCreate(const char *collname, Oid collnamespace,
 							int32 collencoding,
 							const char *collcollate, const char *collctype,
 							const char *colliculocale,
+							const char *collicurules,
 							const char *collversion,
 							bool if_not_exists,
 							bool quiet);
diff --git a/src/include/catalog/pg_database.h b/src/include/catalog/pg_database.h
index 611c95656a..48bc285863 100644
--- a/src/include/catalog/pg_database.h
+++ b/src/include/catalog/pg_database.h
@@ -71,6 +71,9 @@ CATALOG(pg_database,1262,DatabaseRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID
 	/* ICU locale ID */
 	text		daticulocale;
 
+	/* ICU collation rules */
+	text		daticurules BKI_DEFAULT(_null_);
+
 	/* provider-dependent version of collation data */
 	text		datcollversion BKI_DEFAULT(_null_);
 
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index a875942123..989fe26cef 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -95,6 +95,7 @@ typedef struct pg_locale_struct *pg_locale_t;
 extern PGDLLIMPORT struct pg_locale_struct default_locale;
 
 extern void make_icu_collator(const char *iculocstr,
+							  const char *icurules,
 							  struct pg_locale_struct *resultp);
 
 extern pg_locale_t pg_newlocale_from_collation(Oid collid);
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..ce9ed3c8bf 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -1190,6 +1190,36 @@ SELECT 'Goldmann' < 'Götz' COLLATE "de-x-icu", 'Goldmann' > 'Götz' COLLATE tes
  t        | t
 (1 row)
 
+-- rules
+CREATE COLLATION testcoll_rules1 (provider = icu, locale = '', rules = '&a < g');
+CREATE TABLE test7 (a text);
+-- example from https://unicode-org.github.io/icu/userguide/collation/customization/#syntax
+INSERT INTO test7 VALUES ('Abernathy'), ('apple'), ('bird'), ('Boston'), ('Graham'), ('green');
+SELECT * FROM test7 ORDER BY a COLLATE "en-x-icu";
+     a     
+-----------
+ Abernathy
+ apple
+ bird
+ Boston
+ Graham
+ green
+(6 rows)
+
+SELECT * FROM test7 ORDER BY a COLLATE testcoll_rules1;
+     a     
+-----------
+ Abernathy
+ apple
+ green
+ bird
+ Boston
+ Graham
+(6 rows)
+
+DROP TABLE test7;
+CREATE COLLATION testcoll_rulesx (provider = icu, locale = '', rules = '!!wrong!!');
+ERROR:  could not open collator for locale "" with rules "!!wrong!!": U_INVALID_FORMAT_ERROR
 -- nondeterministic collations
 CREATE COLLATION ctest_det (provider = icu, locale = '', deterministic = true);
 CREATE COLLATION ctest_nondet (provider = icu, locale = '', deterministic = false);
diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql
index b0ddc7db44..aa95c1ec42 100644
--- a/src/test/regress/sql/collate.icu.utf8.sql
+++ b/src/test/regress/sql/collate.icu.utf8.sql
@@ -472,6 +472,19 @@ CREATE COLLATION testcoll_de_phonebook (provider = icu, locale = 'de@collation=p
 SELECT 'Goldmann' < 'Götz' COLLATE "de-x-icu", 'Goldmann' > 'Götz' COLLATE testcoll_de_phonebook;
 
 
+-- rules
+
+CREATE COLLATION testcoll_rules1 (provider = icu, locale = '', rules = '&a < g');
+CREATE TABLE test7 (a text);
+-- example from https://unicode-org.github.io/icu/userguide/collation/customization/#syntax
+INSERT INTO test7 VALUES ('Abernathy'), ('apple'), ('bird'), ('Boston'), ('Graham'), ('green');
+SELECT * FROM test7 ORDER BY a COLLATE "en-x-icu";
+SELECT * FROM test7 ORDER BY a COLLATE testcoll_rules1;
+DROP TABLE test7;
+
+CREATE COLLATION testcoll_rulesx (provider = icu, locale = '', rules = '!!wrong!!');
+
+
 -- nondeterministic collations
 
 CREATE COLLATION ctest_det (provider = icu, locale = '', deterministic = true);
-- 
2.38.1



Attachments:

  [text/plain] 0001-Allow-tailoring-of-ICU-locales-with-custom-rules.patch (22.9K, ../../[email protected]/2-0001-Allow-tailoring-of-ICU-locales-with-custom-rules.patch)
  download | inline diff:
From b0d42407a60e116d3ccb0ed04505aa362f8a6a1d Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Wed, 14 Dec 2022 10:15:03 +0100
Subject: [PATCH] Allow tailoring of ICU locales with custom rules

This exposes the ICU facility to add custom collation rules to a
standard collation.
---
 doc/src/sgml/catalogs.sgml                    | 18 +++++++
 doc/src/sgml/ref/create_collation.sgml        | 22 +++++++++
 doc/src/sgml/ref/create_database.sgml         | 12 +++++
 src/backend/catalog/pg_collation.c            |  5 ++
 src/backend/commands/collationcmds.c          | 21 ++++++--
 src/backend/commands/dbcommands.c             | 49 +++++++++++++++++--
 src/backend/utils/adt/pg_locale.c             | 41 +++++++++++++++-
 src/backend/utils/init/postinit.c             | 11 ++++-
 src/include/catalog/pg_collation.h            |  2 +
 src/include/catalog/pg_database.h             |  3 ++
 src/include/utils/pg_locale.h                 |  1 +
 .../regress/expected/collate.icu.utf8.out     | 30 ++++++++++++
 src/test/regress/sql/collate.icu.utf8.sql     | 13 +++++
 13 files changed, 219 insertions(+), 9 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9316b811ac..afa9f28ef9 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2428,6 +2428,15 @@ <title><structname>pg_collation</structname> Columns</title>
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>collicurules</structfield> <type>text</type>
+      </para>
+      <para>
+       ICU collation rules for this collation object
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>collversion</structfield> <type>text</type>
@@ -3106,6 +3115,15 @@ <title><structname>pg_database</structname> Columns</title>
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>daticurules</structfield> <type>text</type>
+      </para>
+      <para>
+       ICU collation rules for this database
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>datcollversion</structfield> <type>text</type>
diff --git a/doc/src/sgml/ref/create_collation.sgml b/doc/src/sgml/ref/create_collation.sgml
index 58f5f0cd63..2c7266107e 100644
--- a/doc/src/sgml/ref/create_collation.sgml
+++ b/doc/src/sgml/ref/create_collation.sgml
@@ -27,6 +27,7 @@
     [ LC_CTYPE = <replaceable>lc_ctype</replaceable>, ]
     [ PROVIDER = <replaceable>provider</replaceable>, ]
     [ DETERMINISTIC = <replaceable>boolean</replaceable>, ]
+    [ RULES = <replaceable>rules</replaceable>, ]
     [ VERSION = <replaceable>version</replaceable> ]
 )
 CREATE COLLATION [ IF NOT EXISTS ] <replaceable>name</replaceable> FROM <replaceable>existing_collation</replaceable>
@@ -149,6 +150,19 @@ <title>Parameters</title>
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><replaceable>rules</replaceable></term>
+
+     <listitem>
+      <para>
+       Specifies additional collation rules to customize the behavior of the
+       collation.  This is supported for ICU only.  See <ulink
+       url="https://unicode-org.github.io/icu/userguide/collation/customization/"/>
+       for details on the syntax.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><replaceable>version</replaceable></term>
 
@@ -228,6 +242,14 @@ <title>Examples</title>
 </programlisting>
   </para>
 
+  <para>
+   To create a collation using the ICU provider, based on the English ICU
+   locale, with custom rules:
+<programlisting>
+<![CDATA[CREATE COLLATION en_custom (provider = icu, locale = 'en', rules = '&a < g');]]>
+</programlisting>
+  </para>
+
   <para>
    To create a collation from an existing collation:
 <programlisting>
diff --git a/doc/src/sgml/ref/create_database.sgml b/doc/src/sgml/ref/create_database.sgml
index ea38c64731..aa6f121a81 100644
--- a/doc/src/sgml/ref/create_database.sgml
+++ b/doc/src/sgml/ref/create_database.sgml
@@ -192,6 +192,18 @@ <title>Parameters</title>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="parameter">icu_rules</replaceable></term>
+      <listitem>
+       <para>
+        Specifies additional collation rules to customize the behavior of the
+        collation.  This is supported for ICU only.  See <ulink
+        url="https://unicode-org.github.io/icu/userguide/collation/customization/"/>
+        for details on the syntax.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable>locale_provider</replaceable></term>
 
diff --git a/src/backend/catalog/pg_collation.c b/src/backend/catalog/pg_collation.c
index aa8fbe1a98..7ed9de3891 100644
--- a/src/backend/catalog/pg_collation.c
+++ b/src/backend/catalog/pg_collation.c
@@ -50,6 +50,7 @@ CollationCreate(const char *collname, Oid collnamespace,
 				int32 collencoding,
 				const char *collcollate, const char *collctype,
 				const char *colliculocale,
+				const char *collicurules,
 				const char *collversion,
 				bool if_not_exists,
 				bool quiet)
@@ -194,6 +195,10 @@ CollationCreate(const char *collname, Oid collnamespace,
 		values[Anum_pg_collation_colliculocale - 1] = CStringGetTextDatum(colliculocale);
 	else
 		nulls[Anum_pg_collation_colliculocale - 1] = true;
+	if (collicurules)
+		values[Anum_pg_collation_collicurules - 1] = CStringGetTextDatum(collicurules);
+	else
+		nulls[Anum_pg_collation_collicurules - 1] = true;
 	if (collversion)
 		values[Anum_pg_collation_collversion - 1] = CStringGetTextDatum(collversion);
 	else
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index 81e54e0ce6..50f16f2764 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -64,10 +64,12 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 	DefElem    *lcctypeEl = NULL;
 	DefElem    *providerEl = NULL;
 	DefElem    *deterministicEl = NULL;
+	DefElem    *rulesEl = NULL;
 	DefElem    *versionEl = NULL;
 	char	   *collcollate;
 	char	   *collctype;
 	char	   *colliculocale;
+	char	   *collicurules;
 	bool		collisdeterministic;
 	int			collencoding;
 	char		collprovider;
@@ -99,6 +101,8 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 			defelp = &providerEl;
 		else if (strcmp(defel->defname, "deterministic") == 0)
 			defelp = &deterministicEl;
+		else if (strcmp(defel->defname, "rules") == 0)
+			defelp = &rulesEl;
 		else if (strcmp(defel->defname, "version") == 0)
 			defelp = &versionEl;
 		else
@@ -161,6 +165,12 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 		else
 			colliculocale = NULL;
 
+		datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collicurules, &isnull);
+		if (!isnull)
+			collicurules = TextDatumGetCString(datum);
+		else
+			collicurules = NULL;
+
 		ReleaseSysCache(tp);
 
 		/*
@@ -182,6 +192,7 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 		collcollate = NULL;
 		collctype = NULL;
 		colliculocale = NULL;
+		collicurules = NULL;
 
 		if (providerEl)
 			collproviderstr = defGetString(providerEl);
@@ -191,6 +202,9 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 		else
 			collisdeterministic = true;
 
+		if (rulesEl)
+			collicurules = defGetString(rulesEl);
+
 		if (versionEl)
 			collversion = defGetString(versionEl);
 
@@ -297,6 +311,7 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e
 							 collcollate,
 							 collctype,
 							 colliculocale,
+							 collicurules,
 							 collversion,
 							 if_not_exists,
 							 false);	/* not quiet */
@@ -710,7 +725,7 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
 			 */
 			collid = CollationCreate(localebuf, nspid, GetUserId(),
 									 COLLPROVIDER_LIBC, true, enc,
-									 localebuf, localebuf, NULL,
+									 localebuf, localebuf, NULL, NULL,
 									 get_collation_actual_version(COLLPROVIDER_LIBC, localebuf),
 									 true, true);
 			if (OidIsValid(collid))
@@ -777,7 +792,7 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
 
 			collid = CollationCreate(alias, nspid, GetUserId(),
 									 COLLPROVIDER_LIBC, true, enc,
-									 locale, locale, NULL,
+									 locale, locale, NULL, NULL,
 									 get_collation_actual_version(COLLPROVIDER_LIBC, locale),
 									 true, true);
 			if (OidIsValid(collid))
@@ -839,7 +854,7 @@ pg_import_system_collations(PG_FUNCTION_ARGS)
 			collid = CollationCreate(psprintf("%s-x-icu", langtag),
 									 nspid, GetUserId(),
 									 COLLPROVIDER_ICU, true, -1,
-									 NULL, NULL, iculocstr,
+									 NULL, NULL, iculocstr, NULL,
 									 get_collation_actual_version(COLLPROVIDER_ICU, iculocstr),
 									 true, true);
 			if (OidIsValid(collid))
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 6eb8742718..65d6ad46ef 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -119,6 +119,7 @@ static bool get_db_info(const char *name, LOCKMODE lockmode,
 						int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
 						TransactionId *dbFrozenXidP, MultiXactId *dbMinMultiP,
 						Oid *dbTablespace, char **dbCollate, char **dbCtype, char **dbIculocale,
+						char **dbIcurules,
 						char *dbLocProvider,
 						char **dbCollversion);
 static bool have_createdb_privilege(void);
@@ -676,6 +677,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 	char	   *src_collate = NULL;
 	char	   *src_ctype = NULL;
 	char	   *src_iculocale = NULL;
+	char	   *src_icurules = NULL;
 	char		src_locprovider = '\0';
 	char	   *src_collversion = NULL;
 	bool		src_istemplate;
@@ -699,6 +701,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 	DefElem    *dcollate = NULL;
 	DefElem    *dctype = NULL;
 	DefElem    *diculocale = NULL;
+	DefElem    *dicurules = NULL;
 	DefElem    *dlocprovider = NULL;
 	DefElem    *distemplate = NULL;
 	DefElem    *dallowconnections = NULL;
@@ -711,6 +714,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 	char	   *dbcollate = NULL;
 	char	   *dbctype = NULL;
 	char	   *dbiculocale = NULL;
+	char	   *dbicurules = NULL;
 	char		dblocprovider = '\0';
 	char	   *canonname;
 	int			encoding = -1;
@@ -776,6 +780,12 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 				errorConflictingDefElem(defel, pstate);
 			diculocale = defel;
 		}
+		else if (strcmp(defel->defname, "icu_rules") == 0)
+		{
+			if (dicurules)
+				errorConflictingDefElem(defel, pstate);
+			dicurules = defel;
+		}
 		else if (strcmp(defel->defname, "locale_provider") == 0)
 		{
 			if (dlocprovider)
@@ -959,7 +969,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 					 &src_dboid, &src_owner, &src_encoding,
 					 &src_istemplate, &src_allowconn,
 					 &src_frozenxid, &src_minmxid, &src_deftablespace,
-					 &src_collate, &src_ctype, &src_iculocale, &src_locprovider,
+					 &src_collate, &src_ctype, &src_iculocale, &src_icurules, &src_locprovider,
 					 &src_collversion))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_DATABASE),
@@ -1007,6 +1017,8 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 		dblocprovider = src_locprovider;
 	if (dbiculocale == NULL && dblocprovider == COLLPROVIDER_ICU)
 		dbiculocale = src_iculocale;
+	if (dbicurules == NULL && dblocprovider == COLLPROVIDER_ICU)
+		dbicurules = src_icurules;
 
 	/* Some encodings are client only */
 	if (!PG_VALID_BE_ENCODING(encoding))
@@ -1098,6 +1110,9 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 
 		if (dblocprovider == COLLPROVIDER_ICU)
 		{
+			char	   *val1;
+			char	   *val2;
+
 			Assert(dbiculocale);
 			Assert(src_iculocale);
 			if (strcmp(dbiculocale, src_iculocale) != 0)
@@ -1106,6 +1121,19 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 						 errmsg("new ICU locale (%s) is incompatible with the ICU locale of the template database (%s)",
 								dbiculocale, src_iculocale),
 						 errhint("Use the same ICU locale as in the template database, or use template0 as template.")));
+
+			val1 = dbicurules;
+			if (!val1)
+				val1 = "";
+			val2 = src_icurules;
+			if (!val2)
+				val2 = "";
+			if (strcmp(val1, val2) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("new ICU collation rules (%s) are incompatible with the ICU collation rules of the template database (%s)",
+								val1, val2),
+						 errhint("Use the same ICU collation rules as in the template database, or use template0 as template.")));
 		}
 	}
 
@@ -1314,6 +1342,10 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 		new_record[Anum_pg_database_daticulocale - 1] = CStringGetTextDatum(dbiculocale);
 	else
 		new_record_nulls[Anum_pg_database_daticulocale - 1] = true;
+	if (dbicurules)
+		new_record[Anum_pg_database_daticurules - 1] = CStringGetTextDatum(dbicurules);
+	else
+		new_record_nulls[Anum_pg_database_daticurules - 1] = true;
 	if (dbcollversion)
 		new_record[Anum_pg_database_datcollversion - 1] = CStringGetTextDatum(dbcollversion);
 	else
@@ -1527,7 +1559,7 @@ dropdb(const char *dbname, bool missing_ok, bool force)
 	pgdbrel = table_open(DatabaseRelationId, RowExclusiveLock);
 
 	if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL,
-					 &db_istemplate, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
+					 &db_istemplate, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
 	{
 		if (!missing_ok)
 		{
@@ -1727,7 +1759,7 @@ RenameDatabase(const char *oldname, const char *newname)
 	rel = table_open(DatabaseRelationId, RowExclusiveLock);
 
 	if (!get_db_info(oldname, AccessExclusiveLock, &db_id, NULL, NULL,
-					 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
+					 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_DATABASE),
 				 errmsg("database \"%s\" does not exist", oldname)));
@@ -1837,7 +1869,7 @@ movedb(const char *dbname, const char *tblspcname)
 	pgdbrel = table_open(DatabaseRelationId, RowExclusiveLock);
 
 	if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL,
-					 NULL, NULL, NULL, NULL, &src_tblspcoid, NULL, NULL, NULL, NULL, NULL))
+					 NULL, NULL, NULL, NULL, &src_tblspcoid, NULL, NULL, NULL, NULL, NULL, NULL))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_DATABASE),
 				 errmsg("database \"%s\" does not exist", dbname)));
@@ -2600,6 +2632,7 @@ get_db_info(const char *name, LOCKMODE lockmode,
 			int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
 			TransactionId *dbFrozenXidP, MultiXactId *dbMinMultiP,
 			Oid *dbTablespace, char **dbCollate, char **dbCtype, char **dbIculocale,
+			char **dbIcurules,
 			char *dbLocProvider,
 			char **dbCollversion)
 {
@@ -2716,6 +2749,14 @@ get_db_info(const char *name, LOCKMODE lockmode,
 					else
 						*dbIculocale = TextDatumGetCString(datum);
 				}
+				if (dbIcurules)
+				{
+					datum = SysCacheGetAttr(DATABASEOID, tuple, Anum_pg_database_daticurules, &isnull);
+					if (isnull)
+						*dbIcurules = NULL;
+					else
+						*dbIcurules = TextDatumGetCString(datum);
+				}
 				if (dbCollversion)
 				{
 					datum = SysCacheGetAttr(DATABASEOID, tuple, Anum_pg_database_datcollversion, &isnull);
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 2b42d9ccd8..191d2e8a68 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -69,6 +69,7 @@
 
 #ifdef USE_ICU
 #include <unicode/ucnv.h>
+#include <unicode/ustring.h>
 #endif
 
 #ifdef __GLIBC__
@@ -1402,6 +1403,7 @@ struct pg_locale_struct default_locale;
 
 void
 make_icu_collator(const char *iculocstr,
+				  const char *icurules,
 				  struct pg_locale_struct *resultp)
 {
 #ifdef USE_ICU
@@ -1418,6 +1420,35 @@ make_icu_collator(const char *iculocstr,
 	if (U_ICU_VERSION_MAJOR_NUM < 54)
 		icu_set_collation_attributes(collator, iculocstr);
 
+	/*
+	 * If rules are specified, we extract the rules of the standard collation,
+	 * add our own rules, and make a new collator with the combined rules.
+	 */
+	if (icurules)
+	{
+		const UChar *default_rules;
+		UChar	   *agg_rules;
+		UChar	   *my_rules;
+		int32_t		length;
+
+		default_rules = ucol_getRules(collator, &length);
+		icu_to_uchar(&my_rules, icurules, strlen(icurules));
+
+		agg_rules = palloc_array(UChar, u_strlen(default_rules) + u_strlen(my_rules) + 1);
+		u_strcpy(agg_rules, default_rules);
+		u_strcat(agg_rules, my_rules);
+
+		ucol_close(collator);
+
+		status = U_ZERO_ERROR;
+		collator = ucol_openRules(agg_rules, u_strlen(agg_rules),
+								  UCOL_DEFAULT, UCOL_DEFAULT_STRENGTH, NULL, &status);
+		if (U_FAILURE(status))
+			ereport(ERROR,
+					(errmsg("could not open collator for locale \"%s\" with rules \"%s\": %s",
+							iculocstr, icurules, u_errorName(status))));
+	}
+
 	/* We will leak this string if the caller errors later :-( */
 	resultp->info.icu.locale = MemoryContextStrdup(TopMemoryContext, iculocstr);
 	resultp->info.icu.ucol = collator;
@@ -1580,11 +1611,19 @@ pg_newlocale_from_collation(Oid collid)
 		else if (collform->collprovider == COLLPROVIDER_ICU)
 		{
 			const char *iculocstr;
+			const char *icurules;
 
 			datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_colliculocale, &isnull);
 			Assert(!isnull);
 			iculocstr = TextDatumGetCString(datum);
-			make_icu_collator(iculocstr, &result);
+
+			datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collicurules, &isnull);
+			if (!isnull)
+				icurules = TextDatumGetCString(datum);
+			else
+				icurules = NULL;
+
+			make_icu_collator(iculocstr, icurules, &result);
 		}
 
 		datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collversion,
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index a990c833c5..55a035c062 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -421,10 +421,19 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
 
 	if (dbform->datlocprovider == COLLPROVIDER_ICU)
 	{
+		char	   *icurules;
+
 		datum = SysCacheGetAttr(DATABASEOID, tup, Anum_pg_database_daticulocale, &isnull);
 		Assert(!isnull);
 		iculocale = TextDatumGetCString(datum);
-		make_icu_collator(iculocale, &default_locale);
+
+		datum = SysCacheGetAttr(DATABASEOID, tup, Anum_pg_database_daticurules, &isnull);
+		if (!isnull)
+			icurules = TextDatumGetCString(datum);
+		else
+			icurules = NULL;
+
+		make_icu_collator(iculocale, icurules, &default_locale);
 	}
 	else
 		iculocale = NULL;
diff --git a/src/include/catalog/pg_collation.h b/src/include/catalog/pg_collation.h
index 2190ccb5b8..ad2d767d65 100644
--- a/src/include/catalog/pg_collation.h
+++ b/src/include/catalog/pg_collation.h
@@ -43,6 +43,7 @@ CATALOG(pg_collation,3456,CollationRelationId)
 	text		collcollate BKI_DEFAULT(_null_);	/* LC_COLLATE setting */
 	text		collctype BKI_DEFAULT(_null_);	/* LC_CTYPE setting */
 	text		colliculocale BKI_DEFAULT(_null_);	/* ICU locale ID */
+	text		collicurules BKI_DEFAULT(_null_);	/* ICU collation rules */
 	text		collversion BKI_DEFAULT(_null_);	/* provider-dependent
 													 * version of collation
 													 * data */
@@ -91,6 +92,7 @@ extern Oid	CollationCreate(const char *collname, Oid collnamespace,
 							int32 collencoding,
 							const char *collcollate, const char *collctype,
 							const char *colliculocale,
+							const char *collicurules,
 							const char *collversion,
 							bool if_not_exists,
 							bool quiet);
diff --git a/src/include/catalog/pg_database.h b/src/include/catalog/pg_database.h
index 611c95656a..48bc285863 100644
--- a/src/include/catalog/pg_database.h
+++ b/src/include/catalog/pg_database.h
@@ -71,6 +71,9 @@ CATALOG(pg_database,1262,DatabaseRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID
 	/* ICU locale ID */
 	text		daticulocale;
 
+	/* ICU collation rules */
+	text		daticurules BKI_DEFAULT(_null_);
+
 	/* provider-dependent version of collation data */
 	text		datcollversion BKI_DEFAULT(_null_);
 
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index a875942123..989fe26cef 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -95,6 +95,7 @@ typedef struct pg_locale_struct *pg_locale_t;
 extern PGDLLIMPORT struct pg_locale_struct default_locale;
 
 extern void make_icu_collator(const char *iculocstr,
+							  const char *icurules,
 							  struct pg_locale_struct *resultp);
 
 extern pg_locale_t pg_newlocale_from_collation(Oid collid);
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index d4c8c6de38..ce9ed3c8bf 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -1190,6 +1190,36 @@ SELECT 'Goldmann' < 'Götz' COLLATE "de-x-icu", 'Goldmann' > 'Götz' COLLATE tes
  t        | t
 (1 row)
 
+-- rules
+CREATE COLLATION testcoll_rules1 (provider = icu, locale = '', rules = '&a < g');
+CREATE TABLE test7 (a text);
+-- example from https://unicode-org.github.io/icu/userguide/collation/customization/#syntax
+INSERT INTO test7 VALUES ('Abernathy'), ('apple'), ('bird'), ('Boston'), ('Graham'), ('green');
+SELECT * FROM test7 ORDER BY a COLLATE "en-x-icu";
+     a     
+-----------
+ Abernathy
+ apple
+ bird
+ Boston
+ Graham
+ green
+(6 rows)
+
+SELECT * FROM test7 ORDER BY a COLLATE testcoll_rules1;
+     a     
+-----------
+ Abernathy
+ apple
+ green
+ bird
+ Boston
+ Graham
+(6 rows)
+
+DROP TABLE test7;
+CREATE COLLATION testcoll_rulesx (provider = icu, locale = '', rules = '!!wrong!!');
+ERROR:  could not open collator for locale "" with rules "!!wrong!!": U_INVALID_FORMAT_ERROR
 -- nondeterministic collations
 CREATE COLLATION ctest_det (provider = icu, locale = '', deterministic = true);
 CREATE COLLATION ctest_nondet (provider = icu, locale = '', deterministic = false);
diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql
index b0ddc7db44..aa95c1ec42 100644
--- a/src/test/regress/sql/collate.icu.utf8.sql
+++ b/src/test/regress/sql/collate.icu.utf8.sql
@@ -472,6 +472,19 @@ CREATE COLLATION testcoll_de_phonebook (provider = icu, locale = 'de@collation=p
 SELECT 'Goldmann' < 'Götz' COLLATE "de-x-icu", 'Goldmann' > 'Götz' COLLATE testcoll_de_phonebook;
 
 
+-- rules
+
+CREATE COLLATION testcoll_rules1 (provider = icu, locale = '', rules = '&a < g');
+CREATE TABLE test7 (a text);
+-- example from https://unicode-org.github.io/icu/userguide/collation/customization/#syntax
+INSERT INTO test7 VALUES ('Abernathy'), ('apple'), ('bird'), ('Boston'), ('Graham'), ('green');
+SELECT * FROM test7 ORDER BY a COLLATE "en-x-icu";
+SELECT * FROM test7 ORDER BY a COLLATE testcoll_rules1;
+DROP TABLE test7;
+
+CREATE COLLATION testcoll_rulesx (provider = icu, locale = '', rules = '!!wrong!!');
+
+
 -- nondeterministic collations
 
 CREATE COLLATION ctest_det (provider = icu, locale = '', deterministic = true);
-- 
2.38.1



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
@ 2023-11-21 05:39 Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Peter Smith @ 2023-11-21 05:39 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Here are some review comments for the patch v35-0001.

======
0. GENERAL documentation

I felt that the documentation gave details of the individual changes
(e.g. GUC 'standby_slot_names' and API, CREATE SUBSCRIPTION option,
and pg_replication_slots 'failover' attribute etc.) but there is
nothing that seemed to bring all these parts together to give examples
for user "when" and "how" to make all these parts work. I'm not sure
if there is some overview missing from this patch 00001 or if you are
planning that extra documentation for subsequent patches.

======
Commit message

1.
A new property 'failover' is added at the slot level which
is persistent information which specifies that this logical slot
is enabled to be synced to the physical standbys so that logical
replication can be resumed after failover. It is always false
for physical slots.

~

SUGGESTION
A new property 'failover' is added at the slot level. This is
persistent information to indicate that this logical slot...

~~~

2.
Users can set it during the create subscription or during
pg_create_logical_replication_slot. Examples:

create subscription mysub  connection '..' publication mypub
WITH (failover = true);

--last arg
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

~

2a.
Add a blank line before this

~

2b.
Use uppercase for the SQL

~

2c.
SUGGESTION
Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API.

Ex1.
CREATE SUBSCRIPTION mysub CONNECTION '...' PUBLICATION mypub
WITH (failover = true);

Ex2. (failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

~~~

3.
This 'failover' is displayed as part of pg_replication_slots
view.

~

SUGGESTION
The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

~~~

4.
A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscribers (with failover=true) should go
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).

~

4a.
SUGGESTION
A new GUC standby_slot_names has been added. This is a list of
physical replication slots that logical replication with failover
enabled will wait for.

~

4b.
/no logical replication subscribers/no logical replication subscriptions/

~

4c
/should go ahead of physical/should get ahead of physical/

======
contrib/test_decoding/sql/slot.sql

5.
+
+-- Test logical slots creation with 'failover'=true (last arg)
+SELECT 'init' FROM
pg_create_logical_replication_slot('failover_slot', 'test_decoding',
false, false, true);
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_slot');

How about a couple more simple tests:
a) pass false arg to confirm it is false in the view.
b) according to the docs this failover is optional, so try API without
passing it
c) create a physical slot to confirm it is false in the view.

======
doc/src/sgml/catalogs.sgml

6.
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+

This attribute is very similar to the 'subtwophasestate' so IMO it
would be better to be adjacent to that one in the docs.

(probably this means putting it in the same order in the catalog also,
assuming that is allowed)

======
doc/src/sgml/config.sgml

7.
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must enable
+        <varname>enable_syncslot</varname> for the standbys to receive
+        failover logical slots changes from the primary.
+       </para>

That sentence mentioning 'enable_syncslot' seems premature because
AFAIK that GUC is not introduced until patch 0002. So this part should
be moved into the 0002 patch.

======
doc/src/sgml/ref/alter_subscription.sgml

8.
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column
<structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
    to know the actual two-phase state.
I think the last sentence doesn't make sense anymore because it is no
longer talking about only two-phase state.

BEFORE
See column subtwophasestate and subfailoverstate of pg_subscription to
know the actual two-phase state.

SUGGESTION
See column subtwophasestate and subfailoverstate of pg_subscription to
know the actual states.

======
doc/src/sgml/ref/create_subscription.sgml

9.
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot assocaited with the
subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link
linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+
+        </listitem>
+       </varlistentry>

9a.
/assocaited/associated/

~

9b.
Unnecessary blank line before </listitem>

======
src/backend/commands/subscriptioncmds.c

10.
 #define SUBOPT_ORIGIN 0x00004000
+#define SUBOPT_FAILOVER 0x00008000

Bad indentation

~~~

11. CreateSubscription
+ /*
+ * If only the slot_name is specified, it is possible that the user intends to
+ * use an existing slot on the publisher, so here we enable failover for the
+ * slot if requested.
+ */
+ else if (opts.slot_name && failover_enabled)
+ {
+ walrcv_alter_slot(wrconn, opts.slot_name, opts.failover);
+ ereport(NOTICE,
+ (errmsg("enabled failover for replication slot \"%s\" on publisher",
+ opts.slot_name)));
+ }

11a.
How does this code ensure that *only* slot_name was set (e.g the
comment says "only the slot_name is specified")

~

11b.
Should 3rd arg to walrcv_alter_slot be 'failover_enabled', or maybe just 'true'?

~~~

12. AlterSubscription

+ if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed
when failover is enabled"),
+ errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh =
false, or with copy_data = false, or use DROP/CREATE
SUBSCRIPTION.")));

12a.
This should have a comment like what precedes the sub->twophasestate
error. Or maybe group them both and use the same common comment.

~

12b.
AFAIK when there are messages like this that differ only by
non-translatable things ("failover" option) then that non-translatable
thing should be extracted as a parameter so the messages are common.
And, don't forget to add a /* translator: %s is a subscription option
like 'failover' */ comment.

SUGGESTION like:
errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed
when %s is enabled", "two_phase")
errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed
when %s is enabled", "failover")

~~~

13.
+ if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed
when failover is enabled"),
+ /* translator: %s is an SQL ALTER command */
+ errhint("Use %s with refresh = false, or with copy_data = false, or
use DROP/CREATE SUBSCRIPTION.",
+ isadd ?
+ "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+ "ALTER SUBSCRIPTION ... DROP PUBLICATION")));

Same comment as above #12b.

SUGGESTION like:
errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed
when %s is enabled", "two_phase")
errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed
when %s is enabled", "failover")

~~~

14.
+ /*
+ * See comments above for twophasestate, same holds true for
+ * 'failover'
+ */
+ if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed
when failover is enabled"),
+ errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false,
or use DROP/CREATE SUBSCRIPTION.")));

IMO this is another message where the option should be extracted to
make a common message for the translators. And don't forget to add a
/* translator: %s is a subscription option like 'failover' */ comment.

SUGGESTION like:
errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed
when %s is enabled", "two_phase"),
errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed
when %s is enabled", "failover"),

======
.../libpqwalreceiver/libpqwalreceiver.c

15. libpqrcv_create_slot

+ if (failover)
+ {
+ appendStringInfoString(&cmd, "FAILOVER");
+ if (use_new_options_syntax)
+ appendStringInfoString(&cmd, ", ");
+ else
+ appendStringInfoChar(&cmd, ' ');
+ }

15a.
Isn't failover a new option that is unsupported pre-PG17? Why is it
necessary to support an old-style syntax for something that was not
supported on old servers? (I'm confused).

~

15b.
Also IIRC, this FAILOVER wasn't not listed in the old-style syntax of
doc/src/sgml/protocol.sgml. Was that deliberate?

======
.../replication/logical/logicalfuncs.c

16. pg_logical_slot_get_changes_guts

+ if (XLogRecPtrIsInvalid(upto_lsn))
+ wal_to_wait = end_of_wal;
+ else
+ wal_to_wait = Min(upto_lsn, end_of_wal);
+
+ /*
+ * Wait for specified streaming replication standby servers (if any)
+ * to confirm receipt of WAL upto wal_to_wait.
+ */
+ WalSndWaitForStandbyConfirmation(wal_to_wait);
+

16a.
/WAL upto wal_to_wait./WAL up to wal_to_wait./

~

16b.
Is there another name for this variable (wal_to_wait) that conveys
more meaning? Maybe 'wal_received_pos' or 'wait_for_wal_lsn' or
something better.


======
src/backend/replication/logical/tablesync.c

17. process_syncing_tables_for_apply

  CommandCounterIncrement(); /* make updates visible */
  if (AllTablesyncsReady())
  {
+ char buf[100];
+
+ buf[0] = '\0';
+
+ if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+ strcat(buf, "twophase");
+ if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+ {
+ if (buf[0] != '\0')
+ strcat(buf, " and ");
+ strcat(buf, "failover");
+ }
+
  ereport(LOG,
- (errmsg("logical replication apply worker for subscription \"%s\"
will restart so that two_phase can be enabled",
- MySubscription->name)));
+ (errmsg("logical replication apply worker for subscription \"%s\"
will restart so that %s can be enabled",
+ MySubscription->name, buf)));
  should_exit = true;
  }
~

IMO you cannot build up a log buffer using " and " like this because
the translation would be a mess. IIUC, you might have to do it the
long way with multiple errmsg.

SUGGESTION
twophase_pending = MySubscription->twophasestate ==
LOGICALREP_TWOPHASE_STATE_PENDING;
failover_pending = MySubscription->failoverstate ==
LOGICALREP_FAILOVER_STATE_PENDING;

if (twophase_pending || twophase_pending)
  ereport(LOG,
    twophase_pending && twophase_pending
  /* translator: 'two_phase' or 'failover' are subscription options */
? errmsg("logical replication apply worker for subscription \"%s\"
will restart so that two_phase and failover can be enabled")
: errmsg("logical replication apply worker for subscription \"%s\"
will restart so that %s can be enabled",
         twophase_pending ? "two_phase" : "failover"));

~~~

18. UpdateTwoPhaseFailoverStates

-UpdateTwoPhaseState(Oid suboid, char new_state)
+UpdateTwoPhaseFailoverStates(Oid suboid,
+ bool update_twophase, char new_state_twophase,
+ bool update_failover, char new_state_failover)

Although this function is written to update to *any* specified state,
in practice it only ever seems called to update from PENDING to ENABLE
state and nothing else.

Therefore it can be simplified by not even passing those states, and
by changing the function name like 'EnableTwoPhaseFailoverTriState'


======
src/backend/replication/logical/worker.c

19. File header comment

There is a lot of talk here about two_phase tri-state and the special
ALTER REFRESH considerations for the two-phase transactions. IIUC,
there should be lots of similar commentary for the failover tri-sate
and ALTER REFRESH.

~~~

20.
  * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
  * work.
  */
- if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
- AllTablesyncsReady())
+ twophase_pending = (MySubscription->twophasestate
+ == LOGICALREP_TWOPHASE_STATE_PENDING) ? true : false;
+ failover_pending = (MySubscription->failoverstate
+ == LOGICALREP_FAILOVER_STATE_PENDING) ? true : false;
+

The comment preceding this is only talking about 'two_phase', so
should be expanded to mention also 'failover'

~~~

21. run_apply_worker

+ twophase_pending = (MySubscription->twophasestate
+ == LOGICALREP_TWOPHASE_STATE_PENDING) ? true : false;
+ failover_pending = (MySubscription->failoverstate
+ == LOGICALREP_FAILOVER_STATE_PENDING) ? true : false;

These ternaries are not necessary.

SUGGESTION (has the same meaning)
twophase_pending = (MySubscription->twophasestate ==
LOGICALREP_TWOPHASE_STATE_PENDING);
failover_pending = (MySubscription->failoverstate ==
LOGICALREP_FAILOVER_STATE_PENDING);

~~~

22.
- UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
- MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+ /* Update twophase and/or failover */
+ if (twophase_pending || failover_pending)
+ UpdateTwoPhaseFailoverStates(MySubscription->oid,
+ twophase_pending,
+ LOGICALREP_TWOPHASE_STATE_ENABLED,
+ failover_pending,
+ LOGICALREP_FAILOVER_STATE_ENABLED);
+ if (twophase_pending)
+ MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+ if (failover_pending)
+ MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;

There seem rather too many checks for 'twophase_pending' and 'failover_pending'.

With some refactoring this could be done with less code I think. For example,
1. Unconditionally call UpdateTwoPhaseFailoverStates() but just quick
return if nothing to do
2. Pass address of MySubscription->twophasestate/failoverstate, and
let function UpdateTwoPhaseFailoverStates() set those

======
src/backend/replication/slot.c

23.
+char    *standby_slot_names;
+static List    *standby_slot_names_list = NIL;

Should there be a comment for the new GUC?

~~~

24. ReplicationSlotAlter
+/*
+ * Change the definition of the slot identified by the passed in name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)

/passed in/specified/

/the definition/the failover state/

~~~

25. validate_standby_slots
+
+/*
+ * A helper function to validate slots specified in standby_slot_names GUCs.
+ */
+static bool
+validate_standby_slots(char **newval)

/in standby_slot_names GUCs./in GUC standby_slot_names./

~

26. validate_standby_slots

+ /*
+ * Verify 'type' of slot now.
+ *
+ * Skip check if replication slots' data is not initialized yet i.e. we
+ * are in startup process.
+ */
+ if (!ReplicationSlotCtl)
+ return true;

26a.
This code seems to neglect doing memory cleanup.

+ pfree(rawname);
+ list_free(elemlist);

~

26b.
Indeed, most of this function's return points seem to be neglecting
some memory cleanup, so IMO it would be better to write this function
with some common goto labels that do all this common cleanup:

SUGGESTION
ret_standby_slot_names_ok:
  pfree(rawname);
  list_free(elemlist);
  return true;

ret_standby_slot_names_ng:
  pfree(rawname);
  list_free(elemlist);
  return false;

~

27. validate_standby_slots

+ if (SlotIsLogical(slot))
+ {
+ GUC_check_errdetail("cannot have logical replication slot \"%s\" "
+ "in this parameter", name);
+ list_free(elemlist);
+ return false;
+ }

IIUC, the GUC is for physical replication slots only, so somehow I
felt it was better to keep everything from that (physical)
perspective. YMMV.

SUGGESTION
if (!SlotIsPhysical(slot))
{
  GUC_check_errdetail("\"%s\" is not a physical replication slot", name);
  list_free(elemlist);
  return false;
}

~~~

28. check_standby_slot_names

+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+ if (strcmp(*newval, "") == 0)
+ return true;
+
+ /*
+ * "*" is not accepted as in that case primary will not be able to know
+ * for which all standbys to wait for. Even if we have physical-slots
+ * info, there is no way to confirm whether there is any standby
+ * configured for the known physical slots.
+ */
+ if (strcmp(*newval, "*") == 0)
+ {
+ GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+ *newval);
+ return false;
+ }
+
+ /* Now verify if the specified slots really exist and have correct type */
+ if (!validate_standby_slots(newval))
+ return false;
+
+ *extra = guc_strdup(ERROR, *newval);
+
+ return true;
+}

Is it really necessary to have a special test for the special value
"*" which you are going to reject? I don't see why this should be any
different from checking for other values like "." or "$" or "?" etc.
Why not just let validate_standby_slots() handle all of these?

~~~

29. assign_standby_slot_names

+ /* No value is specified for standby_slot_names. */
+ if (standby_slot_names_cpy == NULL)
+ return;

Is this possible? IIUC the check_standby_slot_names() did:
*extra = guc_strdup(ERROR, *newval);

Maybe this code also needs a similar elog and comment like already in
this function:
/* This should not happen if GUC checked check_standby_slot_names. */

~

30. assign_standby_slot_names

+ char    *standby_slot_names_cpy = extra;

IIUC, the 'extra' was unconditionally guc_strdup()'ed in the check
hook, so should we also free it here before leaving this function?

~~~

31. GetStandbySlotList

+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+ if (copy)
+ return list_copy(standby_slot_names_list);
+ else
+ return standby_slot_names_list;
+}

Why is this better than just exposing the standby_slot_names_list. The
caller can make a copy or not.
e.g. why is calling GetStandbySlotList(true) better than just doing
list_copy(standby_slot_names_list)?

======
src/backend/replication/walsender.c

32. parseCreateReplSlotOptions

 static void WalSndSegmentOpen(XLogReaderState *state, XLogSegNo nextSegNo,
    TimeLineID *tli_p);

-
 /* Initialize walsender process before entering the main command loop */
 void

~

Unnecessary changing of whitespace unrelated to this patch.

~~~

33. WalSndWakeupNeeded

+/*
+ * Does this Wal Sender need to wake up logical walsender.
+ *
+ * Check if the physical slot of this walsender is specified in
+ * standby_slot_names GUC.
+ */
+static bool
+WalSndWakeupNeeded()

/Wal Sender/physical walsender process/  (maybe??)

~~~

34. WalSndFilterStandbySlots

+ /* Log warning if no active_pid for this physical slot */
+ if (slot->active_pid == 0)
+ ereport(WARNING,

Other nearby code is guarding the slot in case it was NULL, so why not
here? Is it a potential NPE?

~~~

35.
+ /*
+ * If logical slot name is given in standby_slot_names, give WARNING
+ * and skip it. Since it is harmless, so WARNING should be enough, no
+ * need to error-out.
+ */
+ else if (SlotIsLogical(slot))
+ warningfmt = _("cannot have logical replication slot \"%s\" in
parameter \"%s\", ignoring");

Is this possible? Doesn't the function 'validate_standby_slots' called
by the GUC hook prevent specifying logical slots in the GUC? Maybe
this warning should be changed to Assert?

~~~

36.
+ /*
+ * Reaching here indicates that either the slot has passed the
+ * wait_for_lsn or there is an issue with the slot that requires a
+ * warning to be reported.
+ */
+ if (warningfmt)
+ ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+ standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);

If something was wrong with the slot that required a warning, is it
really OK to remove this slot from the list? This seems contrary to
the function comment which only talks about removing slots that have
caught up.

~~~

37. WalSndWaitForStandbyConfirmation

+/*
+ * Wait for physical standby to confirm receiving given lsn.
+ *
+ * Here logical walsender associated with failover logical slot waits
+ * for physical standbys corresponding to physical slots specified in
+ * standby_slot_names GUC.
+ */

/given/the given/

~~~

38. WalSndWaitForStandbyConfirmation

+ ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+ for (;;)

This ConditionVariablePrepareToSleep was already called in the
WalSndWait() function. Did it need to be called 2 times?

~~~

39.
+ WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+ /* Exit if done waiting for every slot. */
+ if (standby_slots == NIL)
+ break;
+
+ CHECK_FOR_INTERRUPTS();
+
+ if (ConfigReloadPending)
+ {
+ ConfigReloadPending = false;
+ WalSndRereadConfigAndSlots(&standby_slots);
+ }

Shouldn't all the config reload stuff come first before the filter and
NIL check, just in case after the reload there is nothing to do?
Otherwise, it might cause unnecessary sleep.

~~~

40. WalSndWaitForWal

 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, the
+ * function also waits for all the specified streaming replication standby
+ * servers to confirm receipt of WAL upto RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)

~

/upto/up to/

~~~

41.
  /*
- * Fast path to avoid acquiring the spinlock in case we already know we
- * have enough WAL available. This is particularly interesting if we're
- * far behind.
+ * Check if all the standby servers have confirmed receipt of WAL upto
+ * RecentFlushPtr if we already know we have enough WAL available.
+ *
+ * Note that we cannot directly return without checking the status of
+ * standby servers because the standby_slot_names may have changed, which
+ * means there could be new standby slots in the list that have not yet
+ * caught up to the RecentFlushPtr.
  */
  if (RecentFlushPtr != InvalidXLogRecPtr &&
  loc <= RecentFlushPtr)
- return RecentFlushPtr;
+ {
+ WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);

41a.
/upto/up to/

~

41b.
IMO there is some missing information in this comment because it
wasn't clear to me that calling WalSndFilterStandbySlots was going to
side-efect that list to give it a different meaning. e.g. it seems it
no longer means "standby slots" but instead means something like
"standby slots that are not caught up". Perhaps that local variable
can have a name that helps to convey that better?

~~~

42.
+ /*
+ * Fast path to entering the loop in case we already know we have
+ * enough WAL available and all the standby servers has confirmed
+ * receipt of WAL upto RecentFlushPtr. This is particularly
+ * interesting if we're far behind.
+ */
+ if (standby_slots == NIL)
+ return RecentFlushPtr;

42a.
/has/have/

~

42b.
For entering what loop? There's no context for this comment. I assume
it means the loop that comes later in this function, but then isn't
this a typo? /Fast path to entering the loop/Fast path to avoid
entering the loop/. Alternatively, just don't even mention the loop -
just say "Quick return" etc.

~~~

43. WalSndWait

-WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
+WalSndWait(uint32 socket_events, long timeout, uint32 wait_event,
+    bool wait_for_standby)

Does this need the 'wait_for_standby' parameter? AFAICT this was only
set true when the event enum was
WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION, so why do we need
an extra boolean to be passed when there is already enough information
in the event to know when it is waiting for standby?

~~~

44.
+ if (wait_for_standby)
+ ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+ else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
  ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
  else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
  ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
~

A walsender is either physical or logical, but here the
'wait_for_standby' flag overrides everything. Is it OK for this to be
if/else/else or should this code call for wal_confirm_rcv_cv AND the
other one?

e.g. The function comment for WalSndWaitForWal said "the function also waits..."

======
src/backend/utils/misc/guc_tables.c

45.
+ {"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+ gettext_noop("List of streaming replication standby server slot "
+ "names that logical walsenders waits for."),

/walsenders waits for./walsender processes will wait for./

======
src/backend/utils/misc/postgresql.conf.sample

46.
+#standby_slot_names = '' # streaming replication standby server slot names that
+ # logical walsenders waits for

(same as the msg in guc_tables)

/walsenders waits for/walsender processes will wait for/


======
src/bin/pg_upgrade/info.c

47.
@@ -681,6 +681,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo,
bool live_check)
  int i_twophase;
  int i_caught_up;
  int i_invalid;
+ int i_failover;

~

IMO it would be better if all these were coded to use the same order
as the SQL -- so put each of the "failover" code immediately after the
'two_phase" code.

~~~

48.
@@ -689,6 +690,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo,
bool live_check)
  i_twophase = PQfnumber(res, "two_phase");
  i_caught_up = PQfnumber(res, "caught_up");
  i_invalid = PQfnumber(res, "invalid");
+ i_failover = PQfnumber(res, "failover");

~

ditto #47.

~~~

49.
@@ -699,6 +701,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo,
bool live_check)
  curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
  curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
  curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
+ curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);

~

ditto #47.
======
src/bin/pg_upgrade/pg_upgrade.c

50.
+ if (GET_MAJOR_VERSION(new_cluster.major_version) >= 1700)
+ appendPQExpBuffer(query, ", false, %s, %s);",
+   slot_info->two_phase ? "true" : "false",
+   slot_info->failover ? "true" : "false");
+ else
+ appendPQExpBuffer(query, ", false, %s);",
+   slot_info->two_phase ? "true" : "false");

IMO this would be easier to read if it was written the other way around like

if (GET_MAJOR_VERSION(new_cluster.major_version) < 1700)
  ... old args
else
  ... new args

======
src/bin/pg_upgrade/pg_upgrade.h

51.
+ bool failover; /* is the slot designated to be synced
+ * to the physical standby */
 } LogicalSlotInfo;

The comment is missing a question mark (?) which the others have.

======
src/bin/psql/describe.c

52.
    ", suborigin AS \"%s\"\n"
    ", subpasswordrequired AS \"%s\"\n"
-   ", subrunasowner AS \"%s\"\n",
+   ", subrunasowner AS \"%s\"\n"
+   ", subfailoverstate AS \"%s\"\n",
    gettext_noop("Origin"),
    gettext_noop("Password required"),
-   gettext_noop("Run as owner?"));
+   gettext_noop("Run as owner?"),
+   gettext_noop("Enable failover?"));

I didn't think "Enable failover?" should not have a question mark. IMO
"run as owner?" is the odd one out so should not have been copied.
Anyway,  the subfailoverstate is a 'state' rather than a simple
boolean, so it should be more like subtwophasestate than anything
else.

======
src/bin/psql/tab-complete.c

53.
  COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
    "disable_on_error", "enabled", "origin",
    "password_required", "run_as_owner", "slot_name",
-   "streaming", "synchronous_commit", "two_phase");
+   "streaming", "synchronous_commit", "two_phase",
+   "failover");

All these tab completion options are supposed to be in alphabetical
order, so this 'failover' has been added in the wrong position.

======
src/include/catalog/pg_subscription.h

54.
/*
 * two_phase tri-state values. See comments atop worker.c to know more about
 * these states.
 */
#define LOGICALREP_TWOPHASE_STATE_DISABLED 'd'
#define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
#define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'

#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'

~

54a.
There should either be another comment (like the 'two_phase tri-state'
one) added for the FAILOVER states or that existing comment should be
expanded so that it also mentions the 'failover' tri-states.

~

54b.
Idea: If you are willing to change the constant names (not the values)
of the current tri-states then now both the 'two_phase' and 'failover'
could share them -- I also think this might give the ability to create
macros (if wanted) or to share more code instead of always handling
failover and two_phase separately.

SUGGESTION
#define LOGICALREP_TRISTATE_DISABLED 'd'
#define LOGICALREP_TRISTATE_PENDING 'p'
#define LOGICALREP_TRISTATE_ENABLED 'e'

~

54c.
The header comment at the top of worker.c should give more details
about the 'failover' tri-state. (also mentioned in another review
comment)

~~~

55. FormData_pg_subscription

+ char subfailoverstate; /* Enable Failover State */
+

/Enable Failover State/Failover state/

======
src/include/replication/slot.h

56.
+
+ /*
+ * Is this a failover slot (sync candidate for physical standbys)?
+ * Relevant for logical slots on the primary server.
+ */
+ bool failover;
 } ReplicationSlotPersistentData;

~

/Relevant/Only relevant/

======
src/include/replication/walreceiver.h

57.
+#define walrcv_create_slot(conn, slotname, temporary, two_phase,
failover, snapshot_action, lsn) \
+ WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary,
two_phase, failover,  snapshot_action, lsn)

double whitespace after the 'failover' parameter?

======
src/include/replication/walsender_private.h

58.
  ConditionVariable wal_flush_cv;
  ConditionVariable wal_replay_cv;

+ ConditionVariable wal_confirm_rcv_cv;

Should this new field have a comment? Or should it be grouped with the
2 preceding fields (if that same group comment is valid for all of
them)?

======
Kind Regards,
Peter Smith.
Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
@ 2023-11-27 04:02 ` Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-11-27 04:02 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Tuesday, November 21, 2023 1:39 PM Peter Smith <[email protected]> wrote:

Hi,

Thanks for the comments.

> 
> ======
> doc/src/sgml/catalogs.sgml
> 
> 6.
> +     <row>
> +      <entry role="catalog_table_entry"><para role="column_definition">
> +       <structfield>subfailoverstate</structfield> <type>char</type>
> +      </para>
> +      <para>
> +       State codes for failover mode:
> +       <literal>d</literal> = disabled,
> +       <literal>p</literal> = pending enablement,
> +       <literal>e</literal> = enabled
> +      </para></entry>
> +     </row>
> +
> 
> This attribute is very similar to the 'subtwophasestate' so IMO it would be
> better to be adjacent to that one in the docs.
> 
> (probably this means putting it in the same order in the catalog also, assuming
> that is allowed)

It's allowed, but I think the functionality of two fields are different and I didn’t find
the correlation between two fields except for the type of value. So I didn't change the order.

> 
> ~~~
> 
> 12. AlterSubscription
> 
> + if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED &&
> + opts.copy_data) ereport(ERROR,
> + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> + errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed
> when failover is enabled"),
> + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh =
> false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
> 
> ~
> 
> 12b.
> AFAIK when there are messages like this that differ only by non-translatable
> things ("failover" option) then that non-translatable thing should be extracted
> as a parameter so the messages are common.
> And, don't forget to add a /* translator: %s is a subscription option like
> 'failover' */ comment.
> 
> SUGGESTION like:
> errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed
> when %s is enabled", "two_phase") errmsg("ALTER SUBSCRIPTION with refresh
> and copy_data is not allowed when %s is enabled", "failover")

I am not sure about changing the existing message here, I feel you can start a
separate thread to change the twophase related messages, and we can change accordingly
if it's accepted.

> 
> ======
> .../libpqwalreceiver/libpqwalreceiver.c
> 
> 15. libpqrcv_create_slot
> 
> + if (failover)
> + {
> + appendStringInfoString(&cmd, "FAILOVER"); if (use_new_options_syntax)
> + appendStringInfoString(&cmd, ", "); else appendStringInfoChar(&cmd, '
> + '); }
> 
> 15a.
> Isn't failover a new option that is unsupported pre-PG17? Why is it necessary to
> support an old-style syntax for something that was not supported on old
> servers? (I'm confused).
> 
> ~
> 
> 15b.
> Also IIRC, this FAILOVER wasn't not listed in the old-style syntax of
> doc/src/sgml/protocol.sgml. Was that deliberate?

We don't support FAILOVER for old-style syntax and pre-PG17,
libpqrcv_create_slot is only building the replication command string and we
will add failover in the string so that the publisher will report errors if it
doesn't support these options ,the same is true for two_phase.


> ~~~
> 
> 24. ReplicationSlotAlter
> +/*
> + * Change the definition of the slot identified by the passed in name.
> + */
> +void
> +ReplicationSlotAlter(const char *name, bool failover)
> 
> /the definition/the failover state/

I kept this as it's a general function but we only
support changing failover state for now.


> ~~~
> 
> 28. check_standby_slot_names
> 
> +bool
> +check_standby_slot_names(char **newval, void **extra, GucSource source)
> +{  if (strcmp(*newval, "") == 0)  return true;
> +
> + /*
> + * "*" is not accepted as in that case primary will not be able to know
> + * for which all standbys to wait for. Even if we have physical-slots
> + * info, there is no way to confirm whether there is any standby
> + * configured for the known physical slots.
> + */
> + if (strcmp(*newval, "*") == 0)
> + {
> + GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
> + *newval); return false; }
> +
> + /* Now verify if the specified slots really exist and have correct
> + type */ if (!validate_standby_slots(newval)) return false;
> +
> + *extra = guc_strdup(ERROR, *newval);
> +
> + return true;
> +}
> 
> Is it really necessary to have a special test for the special value "*" which you are
> going to reject? I don't see why this should be any different from checking for
> other values like "." or "$" or "?" etc.
> Why not just let validate_standby_slots() handle all of these?

SplitIdentifierString() does not give error for '*' and '*' can be considered
as valid value which if accepted can mislead user that all the standbys's slots
are now considered, which is not the case here. So we want to explicitly call
out this case i.e. '*' is not accepted as valid value for standby_slot_names.

> 
> ~~~
> 
> 29. assign_standby_slot_names
> 
> + /* No value is specified for standby_slot_names. */ if
> + (standby_slot_names_cpy == NULL) return;
> 
> Is this possible? IIUC the check_standby_slot_names() did:
> *extra = guc_strdup(ERROR, *newval);
> 
> Maybe this code also needs a similar elog and comment like already in this
> function:
> /* This should not happen if GUC checked check_standby_slot_names. */

This case is possible, standby_slot_names_cpy(e.g. extra pointer) is NULL if no
value("") is specified for the GUC.(see the code in check_standby_slot_names).

> ~
> 
> 30. assign_standby_slot_names
> 
> + char    *standby_slot_names_cpy = extra;
> 
> IIUC, the 'extra' was unconditionally guc_strdup()'ed in the check hook, so
> should we also free it here before leaving this function?

No, as mentioned in src/backend/utils/misc/README, the space of extra
will be automatically freed when the associated GUC setting is no longer of interest.

> 
> ~~~
> 
> 31. GetStandbySlotList
> 
> +/*
> + * Return a copy of standby_slot_names_list if the copy flag is set to
> +true,
> + * otherwise return the original list.
> + */
> +List *
> +GetStandbySlotList(bool copy)
> +{
> + if (copy)
> + return list_copy(standby_slot_names_list);
> + else
> + return standby_slot_names_list;
> +}
> 
> Why is this better than just exposing the standby_slot_names_list. The caller
> can make a copy or not.
> e.g. why is calling GetStandbySlotList(true) better than just doing
> list_copy(standby_slot_names_list)?

I think either way is fine, but I prefer not to add one global
variable if possible.

> 
> ~~~
> 
> 34. WalSndFilterStandbySlots
> 
> + /* Log warning if no active_pid for this physical slot */ if
> + (slot->active_pid == 0) ereport(WARNING,
> 
> Other nearby code is guarding the slot in case it was NULL, so why not here? Is
> it a potential NPE?

I think it will not pass the check for restart_lsn before the active_pid if slot is NULL.

> 
> ~~~
> 
> 35.
> + /*
> + * If logical slot name is given in standby_slot_names, give WARNING
> + * and skip it. Since it is harmless, so WARNING should be enough, no
> + * need to error-out.
> + */
> + else if (SlotIsLogical(slot))
> + warningfmt = _("cannot have logical replication slot \"%s\" in
> parameter \"%s\", ignoring");
> 
> Is this possible? Doesn't the function 'validate_standby_slots' called by the GUC
> hook prevent specifying logical slots in the GUC? Maybe this warning should be
> changed to Assert?

I think user could drop the logical slot and recreate a physical slot with the same name
without changing the GUC.

> 
> ~~~
> 
> 36.
> + /*
> + * Reaching here indicates that either the slot has passed the
> + * wait_for_lsn or there is an issue with the slot that requires a
> + * warning to be reported.
> + */
> + if (warningfmt)
> + ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
> +
> + standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
> 
> If something was wrong with the slot that required a warning, is it really OK to
> remove this slot from the list? This seems contrary to the function comment
> which only talks about removing slots that have caught up.

I think it's OK to remove slots if it's invalidated, dropped, or was
changed to logical one as we don't need to wait for these slots to catch up anymore.

> ~~~
> 
> 41.
>   /*
> - * Fast path to avoid acquiring the spinlock in case we already know we
> - * have enough WAL available. This is particularly interesting if we're
> - * far behind.
> + * Check if all the standby servers have confirmed receipt of WAL upto
> + * RecentFlushPtr if we already know we have enough WAL available.
> + *
> + * Note that we cannot directly return without checking the status of
> + * standby servers because the standby_slot_names may have changed,
> + which
> + * means there could be new standby slots in the list that have not yet
> + * caught up to the RecentFlushPtr.
>   */
>   if (RecentFlushPtr != InvalidXLogRecPtr &&
>   loc <= RecentFlushPtr)
> - return RecentFlushPtr;
> + {
> + WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
> 
> 41b.
> IMO there is some missing information in this comment because it wasn't clear
> to me that calling WalSndFilterStandbySlots was going to side-efect that list to
> give it a different meaning. e.g. it seems it no longer means "standby slots" but
> instead means something like "standby slots that are not caught up". Perhaps
> that local variable can have a name that helps to convey that better?

I am not sure about this, WalSndFilterStandbySlots already indicates it will
filter the slot list which seems clear to me. But if you have better ideas, we can
adjust in next version.

> 
> ~~~
> 
> 44.
> + if (wait_for_standby)
> + ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
> + else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
>   ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
>   else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
>   ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
> ~
> 
> A walsender is either physical or logical, but here the 'wait_for_standby' flag
> overrides everything. Is it OK for this to be if/else/else or should this code call
> for wal_confirm_rcv_cv AND the other one?

No, we cannot prepare to sleep twice(see the comment in
ConditionVariablePrepareToSleep()).

> ======
> src/include/catalog/pg_subscription.h
> 
> 54.
> /*
>  * two_phase tri-state values. See comments atop worker.c to know more
> about
>  * these states.
>  */
> #define LOGICALREP_TWOPHASE_STATE_DISABLED 'd'
> #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
> #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
> 
> #define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
> #define LOGICALREP_FAILOVER_STATE_PENDING 'p'
> #define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
> 
> ~
> 
> 54a.
> There should either be another comment (like the 'two_phase tri-state'
> one) added for the FAILOVER states or that existing comment should be
> expanded so that it also mentions the 'failover' tri-states.
> 
> ~
> 
> 54b.
> Idea: If you are willing to change the constant names (not the values) of the
> current tri-states then now both the 'two_phase' and 'failover'
> could share them -- I also think this might give the ability to create macros (if
> wanted) or to share more code instead of always handling failover and
> two_phase separately.
> 
> SUGGESTION
> #define LOGICALREP_TRISTATE_DISABLED 'd'
> #define LOGICALREP_TRISTATE_PENDING 'p'
> #define LOGICALREP_TRISTATE_ENABLED 'e'

I am not sure about the idea, but if others also prefer this then
we can adjust the code.


~~~ 
On Wednesday, November 22, 2023 3:42 PM Peter Smith <[email protected]> wrote:
> 6.
> +# The subscription that's up and running and is enabled for failover #
> +doesn't get the data from primary and keeps waiting for the # standby
> +specified in standby_slot_names.
> +$result = $subscriber1->safe_psql('postgres',
> + "SELECT count(*) = 0 FROM tab_int;");
> +is($result, 't', "subscriber1 doesn't get data from primary until
> standby1 acknowledges changes");
> 
> Might it be better to write as "SELECT count(*) = $primary_row_count FROM
> tab_int;" and expect it to return false?

Ensuring the number is 0 looks better to me.

Attach the V38 patch set which addressed all comments in [1][2]
except for the ones that mentioned above.

[1] https://www.postgresql.org/message-id/CAHut%2BPv-yu71ogj_hRi6cCtmD55bsyw7XTxj1Nq8yVFKpY3NDQ%40mail.g...
[2] https://www.postgresql.org/message-id/CAHut%2BPuEGX5kr0xh06yv8ndoAQvDNedoec1OqOq3GMxDN6p%3D9A%40mail...

Best Regards,
Hou zj



Attachments:

  [application/octet-stream] v38-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch (8.0K, ../../OS0PR01MB571646B8186F6A06404BD50194BDA@OS0PR01MB5716.jpnprd01.prod.outlook.com/2-v38-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch)
  download | inline diff:
From efe8d63d54b848308079bcdebefc940b22a75f82 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Tue, 21 Nov 2023 12:12:23 +0800
Subject: [PATCH v38 3/3] Allow slot-sync worker to wait for the cascading
 standbys.

The GUC standby_slot_names is needed to be set on first standby
in order to allow it to wait for confirmation for cascading
standbys before updating logical 'synced' slots in slot-sync worker.
The intent is that the logical slots (synced ones) should not go
ahead of cascading standbys.

For the user created slots on first standby, we already have this wait
logic in place in logical walsender and in pg_logical_slot_get_changes_guts(),
but for synced slots (which can not be consumed yet), we need to make
sure that they are not going ahead of cascading standbys and that is
acheived by introducing the wait in slot-sync worker before we actually
update the slots.
---
 src/backend/replication/logical/slotsync.c | 86 ++++++++++++++++++++--
 src/backend/replication/walsender.c        | 11 +--
 src/include/replication/walsender.h        |  5 ++
 3 files changed, 89 insertions(+), 13 deletions(-)

diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 61bbf42933..6f5b85d6c7 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -83,6 +83,9 @@ typedef struct RemoteSlot
  */
 #define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
 
+static void ProcessSlotSyncInterrupts(WalReceiverConn **wrconn,
+									  List **standby_slots);
+
 /*
  * Wait for remote slot to pass locally reserved position.
  *
@@ -559,6 +562,52 @@ construct_slot_query(StringInfo s)
 					 " WHERE failover and sync_state != 'i'");
 }
 
+/*
+ * Wait for cascading physical standbys corresponding to physical slots
+ * specified in standby_slot_names GUC to confirm receiving given lsn.
+ */
+static void
+wait_for_standby_confirmation(XLogRecPtr wait_for_lsn,
+							  WalReceiverConn *wrconn)
+{
+	List	   *standby_slots;
+
+	/* Nothing to be done */
+	if (strcmp(standby_slot_names, "") == 0)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	for (;;)
+	{
+		int			rc;
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		/*
+		 * This will reload configuration and will refresh the standby_slots
+		 * as well provided standby_slot_names GUC is changed by the user.
+		 */
+		ProcessSlotSyncInterrupts(&wrconn, &standby_slots);
+
+		/*
+		 * XXX: Is waiting for 5 second before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   5000L,
+					   WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+}
+
 /*
  * Synchronize single slot to given position.
  *
@@ -743,6 +792,7 @@ synchronize_slots(WalReceiverConn *wrconn)
 	List	   *remote_slot_list = NIL;
 	MemoryContext oldctx = CurrentMemoryContext;
 	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	XLogRecPtr      max_confirmed_lsn = 0;
 	ListCell   *cell;
 	bool		slot_updated = false;
 	TimestampTz now;
@@ -792,6 +842,9 @@ synchronize_slots(WalReceiverConn *wrconn)
 		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
 		Assert(!isnull);
 
+		if (remote_slot->confirmed_lsn > max_confirmed_lsn)
+			max_confirmed_lsn = remote_slot->confirmed_lsn;
+
 		/*
 		 * It is possible to get null values for LSN and Xmin if slot is
 		 * invalidated on the primary server, so handle accordingly.
@@ -838,6 +891,17 @@ synchronize_slots(WalReceiverConn *wrconn)
 	 */
 	drop_obsolete_slots(remote_slot_list);
 
+	/*
+	 * If there are cascading standbys, wait for their confirmation before we
+	 * update synced logical slots locally.
+	 *
+	 * Instead of waiting on confirmation for lsn of each slot, let us wait
+	 * once for confirmation on max_confirmed_lsn. If that is confirmed by
+	 * each cascading standby, we are good to update all the slots.
+	 */
+	if (remote_slot_list)
+		wait_for_standby_confirmation(max_confirmed_lsn, wrconn);
+
 	/* Now sync the slots locally */
 	foreach(cell, remote_slot_list)
 	{
@@ -895,12 +959,18 @@ remote_connect(void)
  * If primary_conninfo has changed, reconnect to primary.
  */
 static void
-slotsync_reread_config(WalReceiverConn **wrconn)
+slotsync_reread_config(WalReceiverConn **wrconn, List **standby_slots)
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 
-	ConfigReloadPending = false;
-	ProcessConfigFile(PGC_SIGHUP);
+	/*
+	 * Reload configs and recreate the standby_slot_names_list if GUC
+	 * standby_slot_names changed.
+	 */
+	if (standby_slots)
+		WalSndRereadConfigAndReInitSlotList(standby_slots);
+	else
+		ProcessConfigFile(PGC_SIGHUP);
 
 	/* Reconnect if GUC primary_conninfo got changed */
 	if (strcmp(conninfo, PrimaryConnInfo) != 0)
@@ -918,7 +988,8 @@ slotsync_reread_config(WalReceiverConn **wrconn)
  * Interrupt handler for main loop of slot sync worker.
  */
 static void
-ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
+ProcessSlotSyncInterrupts(WalReceiverConn **wrconn,
+						  List **standby_slots)
 {
 	CHECK_FOR_INTERRUPTS();
 
@@ -934,7 +1005,10 @@ ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
 
 
 	if (ConfigReloadPending)
-		slotsync_reread_config(wrconn);
+	{
+		ConfigReloadPending = false;
+		slotsync_reread_config(wrconn, standby_slots);
+	}
 }
 
 /*
@@ -996,7 +1070,7 @@ ReplSlotSyncWorkerMain(Datum main_arg)
 		int			rc;
 		long		naptime;
 
-		ProcessSlotSyncInterrupts(&wrconn);
+		ProcessSlotSyncInterrupts(&wrconn, NULL /* standby_slots */ );
 
 		/* Check if got promoted */
 		if (!RecoveryInProgress())
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index edb3656cd2..db5af0b598 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1608,7 +1608,7 @@ PhysicalWakeupLogicalWalSnd(void)
  * Reload the config file and reinitialize the standby slot list if the GUC
  * standby_slot_names has changed.
  */
-static void
+void
 WalSndRereadConfigAndReInitSlotList(List **standby_slots)
 {
 	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
@@ -1631,13 +1631,12 @@ WalSndRereadConfigAndReInitSlotList(List **standby_slots)
  * This function updates the passed standby_slots list, removing any slots that
  * have already caught up to or surpassed the given wait_for_lsn.
  */
-static void
+void
 WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 {
 	ListCell   *lc;
-	List	   *standby_slots_cpy = *standby_slots;
 
-	foreach(lc, standby_slots_cpy)
+	foreach(lc, *standby_slots)
 	{
 		char	   *name = lfirst(lc);
 		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
@@ -1704,10 +1703,8 @@ WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 		if (warningfmt)
 			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
 
-		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+		*standby_slots = foreach_delete_current(*standby_slots, lc);
 	}
-
-	*standby_slots = standby_slots_cpy;
 }
 
 /*
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 1fcc22a127..c5c4714788 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -15,6 +15,7 @@
 #include <signal.h>
 
 #include "access/xlogdefs.h"
+#include "nodes/pg_list.h"
 
 /*
  * What to do with a snapshot in create replication slot command.
@@ -50,7 +51,11 @@ extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn,
+									 List **standby_slots);
 extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
+extern List *WalSndGetStandbySlots(void);
+extern void WalSndRereadConfigAndReInitSlotList(List **standby_slots);
 
 /*
  * Remember that we want to wakeup walsenders later
-- 
2.30.0.windows.2



  [application/octet-stream] v38-0002-Add-logical-slot-sync-capability-to-the-physical.patch (104.3K, ../../OS0PR01MB571646B8186F6A06404BD50194BDA@OS0PR01MB5716.jpnprd01.prod.outlook.com/3-v38-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From d609ac77dee09f3dc80092b39f61b5583749097d Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Mon, 27 Nov 2023 11:36:09 +0800
Subject: [PATCH v38 2/3] Add logical slot sync capability to the physical
 standby
This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover. All the failover logical
replication slots on the primary (assuming configurations are
appropriate) are automatically created on the physical standbys and
are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The replication launcher on the physical standby starts slot-sync worker
which is then responsible to keep on syncing the logical failover slots
from the primary server.

The nap time of worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. And if
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated. If a logical slot on the primary is valid but is invalidated
on the standby due to conflict (say required rows removed on the primary),
then that slot is dropped and recreated on the standby in next sync-cycle.
It is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently).

Slots synced on the standby can be identified using 'sync_state' column of
pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but waiting for the remote slot on the
     primary server to catch up.
'r': ready for periodic syncs.
---
 doc/src/sgml/config.sgml                      |   35 +-
 doc/src/sgml/logicaldecoding.sgml             |   13 +
 doc/src/sgml/system-views.sgml                |   18 +
 src/backend/access/transam/xlogrecovery.c     |   11 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    5 +-
 .../libpqwalreceiver/libpqwalreceiver.c       |   44 +-
 src/backend/replication/logical/Makefile      |    1 +
 .../replication/logical/applyparallelworker.c |    3 +-
 src/backend/replication/logical/launcher.c    |  667 +++++++++--
 src/backend/replication/logical/logical.c     |   22 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1032 +++++++++++++++++
 src/backend/replication/logical/tablesync.c   |    5 +-
 src/backend/replication/slot.c                |   18 +-
 src/backend/replication/slotfuncs.c           |   33 +-
 src/backend/replication/walsender.c           |    2 +-
 src/backend/storage/lmgr/lwlock.c             |    2 +
 src/backend/storage/lmgr/lwlocknames.txt      |    1 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   12 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/commands/subscriptioncmds.h       |    4 +
 src/include/nodes/replnodes.h                 |    9 +
 src/include/replication/logicallauncher.h     |    9 +-
 src/include/replication/logicalworker.h       |    1 +
 src/include/replication/slot.h                |   19 +-
 src/include/replication/walreceiver.h         |   27 +
 src/include/replication/worker_internal.h     |   55 +-
 src/include/storage/lwlock.h                  |    1 +
 src/test/recovery/t/050_verify_slot_order.pl  |  127 ++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    6 +
 35 files changed, 2056 insertions(+), 151 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 6440378d5c..b2da901ad6 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4357,6 +4357,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must enable
+        <varname>enable_syncslot</varname> for the standbys to receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4550,10 +4556,15 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           A password needs to be provided too, if the sender demands password
           authentication.  It can be provided in the
           <varname>primary_conninfo</varname> string, or in a separate
-          <filename>~/.pgpass</filename> file on the standby server (use
+          <filename>~/.pgpass</filename> file on the standby server. (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          Specify <literal>dbname</literal> in
+          <varname>primary_conninfo</varname> string to allow synchronization
+          of slots from the primary server to the standby server.
+          This will only be used for slot synchronization. It is ignored
+          for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4878,6 +4889,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..90bec06f36 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,19 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and set
+     <varname>enable_syncslot</varname> on the standby. The physical replication
+     slot for the standby should be listed in
+     <varname>standby_slot_names</varname> on the primary to prevent the
+     subscriber from consuming changes faster than the hot standby.
+     Additionally, similar to creating a logical replication slot on the hot
+     standby, <varname>hot_standby_feedback</varname> should be set on the
+     standby and a physical slot between the primary and the standby should be
+     used.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..3631681adc 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,24 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on physical
+      standby which has enabled slots synchronization. For the primary server,
+      its value is always 'none'.
+      </para>
+      <para>
+       State code:
+       <literal>n</literal> = none for user created slots,
+       <literal>i</literal> = sync initiated for the slot but not yet completed,
+       not ready for periodic syncs,
+       <literal>r</literal> = ready for periodic syncs
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c61566666a..8ea6dc799a 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -49,7 +49,9 @@
 #include "postmaster/bgwriter.h"
 #include "postmaster/startup.h"
 #include "replication/slot.h"
+#include "replication/logicallauncher.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1437,15 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 48a9924527..0e039c786f 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -125,11 +125,14 @@ static const struct
 		"ParallelWorkerMain", ParallelWorkerMain
 	},
 	{
-		"ApplyLauncherMain", ApplyLauncherMain
+		"LauncherMain", LauncherMain
 	},
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 336c2bec99..5f82d01e6d 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -413,6 +416,45 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	PQconninfoOption *opt;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
@@ -998,7 +1040,7 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
  */
 static void
 libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
-					 bool failover)
+					bool failover)
 {
 	StringInfoData cmd;
 	PGresult   *res;
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 9b37736f8e..192c9e1860 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -922,7 +922,8 @@ ParallelApplyWorkerMain(Datum main_arg)
 	before_shmem_exit(pa_shutdown, PointerGetDatum(seg));
 
 	SpinLockAcquire(&MyParallelShared->mutex);
-	MyParallelShared->logicalrep_worker_generation = MyLogicalRepWorker->generation;
+	MyParallelShared->logicalrep_worker_generation =
+								MyLogicalRepWorker->hdr.generation;
 	MyParallelShared->logicalrep_worker_slot_no = worker_slot;
 	SpinLockRelease(&MyParallelShared->mutex);
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 501910b445..c0d6cf7e85 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -8,20 +8,27 @@
  *	  src/backend/replication/logical/launcher.c
  *
  * NOTES
- *	  This module contains the logical replication worker launcher which
- *	  uses the background worker infrastructure to start the logical
- *	  replication workers for every enabled subscription.
+ *	  This module contains the replication worker launcher which
+ *	  uses the background worker infrastructure to:
+ *	  a) start the logical replication workers for every enabled subscription
+ *	     when not in standby_mode.
+ *	  b) start the slot sync worker for logical failover slots synchronization
+ *	     from the primary server when in standby_mode.
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
+#include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup.h"
 #include "access/htup_details.h"
 #include "access/tableam.h"
 #include "access/xact.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_database.h"
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_subscription_rel.h"
 #include "funcapi.h"
@@ -44,6 +51,7 @@
 #include "storage/procsignal.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/ps_status.h"
@@ -57,6 +65,9 @@
 int			max_logical_replication_workers = 4;
 int			max_sync_workers_per_subscription = 2;
 int			max_parallel_apply_workers_per_subscription = 2;
+bool		enable_syncslot = false;
+
+SlotSyncWorkerInfo *SlotSyncWorker = NULL;
 
 LogicalRepWorker *MyLogicalRepWorker = NULL;
 
@@ -70,6 +81,7 @@ typedef struct LogicalRepCtxStruct
 	dshash_table_handle last_start_dsh;
 
 	/* Background workers. */
+	SlotSyncWorkerInfo ss_worker;	/* slot sync worker */
 	LogicalRepWorker workers[FLEXIBLE_ARRAY_MEMBER];
 } LogicalRepCtxStruct;
 
@@ -102,6 +114,7 @@ static void logicalrep_launcher_onexit(int code, Datum arg);
 static void logicalrep_worker_onexit(int code, Datum arg);
 static void logicalrep_worker_detach(void);
 static void logicalrep_worker_cleanup(LogicalRepWorker *worker);
+static void slotsync_worker_cleanup(SlotSyncWorkerInfo *worker);
 static int	logicalrep_pa_worker_count(Oid subid);
 static void logicalrep_launcher_attach_dshmem(void);
 static void ApplyLauncherSetWorkerStartTime(Oid subid, TimestampTz start_time);
@@ -178,6 +191,8 @@ get_subscription_list(void)
 }
 
 /*
+ * This is common code for logical workers and slot sync worker.
+ *
  * Wait for a background worker to start up and attach to the shmem context.
  *
  * This is only needed for cleaning up the shared memory in case the worker
@@ -186,12 +201,14 @@ get_subscription_list(void)
  * Returns whether the attach was successful.
  */
 static bool
-WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
+WaitForReplicationWorkerAttach(LogicalWorkerHeader *worker,
 							   uint16 generation,
-							   BackgroundWorkerHandle *handle)
+							   BackgroundWorkerHandle *handle,
+							   LWLock *lock)
 {
 	BgwHandleStatus status;
 	int			rc;
+	bool		is_slotsync_worker = (lock == SlotSyncWorkerLock) ? true : false;
 
 	for (;;)
 	{
@@ -199,27 +216,32 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 
 		CHECK_FOR_INTERRUPTS();
 
-		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		LWLockAcquire(lock, LW_SHARED);
 
 		/* Worker either died or has started. Return false if died. */
 		if (!worker->in_use || worker->proc)
 		{
-			LWLockRelease(LogicalRepWorkerLock);
+			LWLockRelease(lock);
 			return worker->in_use;
 		}
 
-		LWLockRelease(LogicalRepWorkerLock);
+		LWLockRelease(lock);
 
 		/* Check if worker has died before attaching, and clean up after it. */
 		status = GetBackgroundWorkerPid(handle, &pid);
 
 		if (status == BGWH_STOPPED)
 		{
-			LWLockAcquire(LogicalRepWorkerLock, LW_EXCLUSIVE);
+			LWLockAcquire(lock, LW_EXCLUSIVE);
 			/* Ensure that this was indeed the worker we waited for. */
 			if (generation == worker->generation)
-				logicalrep_worker_cleanup(worker);
-			LWLockRelease(LogicalRepWorkerLock);
+			{
+				if (is_slotsync_worker)
+					slotsync_worker_cleanup((SlotSyncWorkerInfo *) worker);
+				else
+					logicalrep_worker_cleanup((LogicalRepWorker *) worker);
+			}
+			LWLockRelease(lock);
 			return false;
 		}
 
@@ -262,8 +284,8 @@ logicalrep_worker_find(Oid subid, Oid relid, bool only_running)
 		if (isParallelApplyWorker(w))
 			continue;
 
-		if (w->in_use && w->subid == subid && w->relid == relid &&
-			(!only_running || w->proc))
+		if (w->hdr.in_use && w->subid == subid && w->relid == relid &&
+			(!only_running || w->hdr.proc))
 		{
 			res = w;
 			break;
@@ -290,7 +312,8 @@ logicalrep_workers_find(Oid subid, bool only_running)
 	{
 		LogicalRepWorker *w = &LogicalRepCtx->workers[i];
 
-		if (w->in_use && w->subid == subid && (!only_running || w->proc))
+		if (w->hdr.in_use && w->subid == subid &&
+			(!only_running || w->hdr.proc))
 			res = lappend(res, w);
 	}
 
@@ -351,7 +374,7 @@ retry:
 	{
 		LogicalRepWorker *w = &LogicalRepCtx->workers[i];
 
-		if (!w->in_use)
+		if (!w->hdr.in_use)
 		{
 			worker = w;
 			slot = i;
@@ -380,7 +403,7 @@ retry:
 			 * If the worker was marked in use but didn't manage to attach in
 			 * time, clean it up.
 			 */
-			if (w->in_use && !w->proc &&
+			if (w->hdr.in_use && !w->hdr.proc &&
 				TimestampDifferenceExceeds(w->launch_time, now,
 										   wal_receiver_timeout))
 			{
@@ -438,9 +461,9 @@ retry:
 	/* Prepare the worker slot. */
 	worker->type = wtype;
 	worker->launch_time = now;
-	worker->in_use = true;
-	worker->generation++;
-	worker->proc = NULL;
+	worker->hdr.in_use = true;
+	worker->hdr.generation++;
+	worker->hdr.proc = NULL;
 	worker->dbid = dbid;
 	worker->userid = userid;
 	worker->subid = subid;
@@ -457,7 +480,7 @@ retry:
 	TIMESTAMP_NOBEGIN(worker->reply_time);
 
 	/* Before releasing lock, remember generation for future identification. */
-	generation = worker->generation;
+	generation = worker->hdr.generation;
 
 	LWLockRelease(LogicalRepWorkerLock);
 
@@ -510,7 +533,7 @@ retry:
 	{
 		/* Failed to start worker, so clean up the worker slot. */
 		LWLockAcquire(LogicalRepWorkerLock, LW_EXCLUSIVE);
-		Assert(generation == worker->generation);
+		Assert(generation == worker->hdr.generation);
 		logicalrep_worker_cleanup(worker);
 		LWLockRelease(LogicalRepWorkerLock);
 
@@ -522,19 +545,23 @@ retry:
 	}
 
 	/* Now wait until it attaches. */
-	return WaitForReplicationWorkerAttach(worker, generation, bgw_handle);
+	return WaitForReplicationWorkerAttach((LogicalWorkerHeader *) worker,
+										  generation,
+										  bgw_handle,
+										  LogicalRepWorkerLock);
 }
 
 /*
  * Internal function to stop the worker and wait until it detaches from the
- * slot.
+ * slot. It is used for both logical workers and slot sync worker.
  */
 static void
-logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
+logicalrep_worker_stop_internal(LogicalWorkerHeader *worker, int signo,
+								LWLock *lock)
 {
 	uint16		generation;
 
-	Assert(LWLockHeldByMeInMode(LogicalRepWorkerLock, LW_SHARED));
+	Assert(LWLockHeldByMeInMode(lock, LW_SHARED));
 
 	/*
 	 * Remember which generation was our worker so we can check if what we see
@@ -550,7 +577,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 	{
 		int			rc;
 
-		LWLockRelease(LogicalRepWorkerLock);
+		LWLockRelease(lock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
 		rc = WaitLatch(MyLatch,
@@ -564,7 +591,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		}
 
 		/* Recheck worker status. */
-		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		LWLockAcquire(lock, LW_SHARED);
 
 		/*
 		 * Check whether the worker slot is no longer used, which would mean
@@ -591,7 +618,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		if (!worker->proc || worker->generation != generation)
 			break;
 
-		LWLockRelease(LogicalRepWorkerLock);
+		LWLockRelease(lock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
 		rc = WaitLatch(MyLatch,
@@ -604,7 +631,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 			CHECK_FOR_INTERRUPTS();
 		}
 
-		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		LWLockAcquire(lock, LW_SHARED);
 	}
 }
 
@@ -623,7 +650,9 @@ logicalrep_worker_stop(Oid subid, Oid relid)
 	if (worker)
 	{
 		Assert(!isParallelApplyWorker(worker));
-		logicalrep_worker_stop_internal(worker, SIGTERM);
+		logicalrep_worker_stop_internal((LogicalWorkerHeader *) worker,
+										SIGTERM,
+										LogicalRepWorkerLock);
 	}
 
 	LWLockRelease(LogicalRepWorkerLock);
@@ -669,8 +698,10 @@ logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
 	/*
 	 * Only stop the worker if the generation matches and the worker is alive.
 	 */
-	if (worker->generation == generation && worker->proc)
-		logicalrep_worker_stop_internal(worker, SIGINT);
+	if (worker->hdr.generation == generation && worker->hdr.proc)
+		logicalrep_worker_stop_internal((LogicalWorkerHeader *) worker,
+										SIGINT,
+										LogicalRepWorkerLock);
 
 	LWLockRelease(LogicalRepWorkerLock);
 }
@@ -696,14 +727,14 @@ logicalrep_worker_wakeup(Oid subid, Oid relid)
 /*
  * Wake up (using latch) the specified logical replication worker.
  *
- * Caller must hold lock, else worker->proc could change under us.
+ * Caller must hold lock, else worker->hdr.proc could change under us.
  */
 void
 logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
 {
 	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
 
-	SetLatch(&worker->proc->procLatch);
+	SetLatch(&worker->hdr.proc->procLatch);
 }
 
 /*
@@ -718,7 +749,7 @@ logicalrep_worker_attach(int slot)
 	Assert(slot >= 0 && slot < max_logical_replication_workers);
 	MyLogicalRepWorker = &LogicalRepCtx->workers[slot];
 
-	if (!MyLogicalRepWorker->in_use)
+	if (!MyLogicalRepWorker->hdr.in_use)
 	{
 		LWLockRelease(LogicalRepWorkerLock);
 		ereport(ERROR,
@@ -727,7 +758,7 @@ logicalrep_worker_attach(int slot)
 						slot)));
 	}
 
-	if (MyLogicalRepWorker->proc)
+	if (MyLogicalRepWorker->hdr.proc)
 	{
 		LWLockRelease(LogicalRepWorkerLock);
 		ereport(ERROR,
@@ -736,7 +767,7 @@ logicalrep_worker_attach(int slot)
 						"another worker, cannot attach", slot)));
 	}
 
-	MyLogicalRepWorker->proc = MyProc;
+	MyLogicalRepWorker->hdr.proc = MyProc;
 	before_shmem_exit(logicalrep_worker_onexit, (Datum) 0);
 
 	LWLockRelease(LogicalRepWorkerLock);
@@ -771,7 +802,9 @@ logicalrep_worker_detach(void)
 			LogicalRepWorker *w = (LogicalRepWorker *) lfirst(lc);
 
 			if (isParallelApplyWorker(w))
-				logicalrep_worker_stop_internal(w, SIGTERM);
+				logicalrep_worker_stop_internal((LogicalWorkerHeader *) w,
+												SIGTERM,
+												LogicalRepWorkerLock);
 		}
 
 		LWLockRelease(LogicalRepWorkerLock);
@@ -794,10 +827,10 @@ logicalrep_worker_cleanup(LogicalRepWorker *worker)
 	Assert(LWLockHeldByMeInMode(LogicalRepWorkerLock, LW_EXCLUSIVE));
 
 	worker->type = WORKERTYPE_UNKNOWN;
-	worker->in_use = false;
-	worker->proc = NULL;
-	worker->dbid = InvalidOid;
+	worker->hdr.in_use = false;
+	worker->hdr.proc = NULL;
 	worker->userid = InvalidOid;
+	worker->dbid = InvalidOid;
 	worker->subid = InvalidOid;
 	worker->relid = InvalidOid;
 	worker->leader_pid = InvalidPid;
@@ -931,9 +964,18 @@ ApplyLauncherRegister(void)
 	memset(&bgw, 0, sizeof(bgw));
 	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
 		BGWORKER_BACKEND_DATABASE_CONNECTION;
-	bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
+
+	/*
+	 * The launcher now takes care of launching both logical apply workers and
+	 * logical slot sync worker. Thus to cater to the requirements of both,
+	 * start it as soon as a consistent state is reached. This will help
+	 * slot sync worker to start timely on a physical standby while on a
+	 * non-standby server, it holds same meaning as that of
+	 * BgWorkerStart_RecoveryFinished.
+	 */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState;
 	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
-	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ApplyLauncherMain");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "LauncherMain");
 	snprintf(bgw.bgw_name, BGW_MAXLEN,
 			 "logical replication launcher");
 	snprintf(bgw.bgw_type, BGW_MAXLEN,
@@ -1115,13 +1157,455 @@ ApplyLauncherWakeup(void)
 }
 
 /*
- * Main loop for the apply launcher process.
+ * Clean up slot sync worker info.
+ */
+static void
+slotsync_worker_cleanup(SlotSyncWorkerInfo *worker)
+{
+	Assert(LWLockHeldByMeInMode(SlotSyncWorkerLock, LW_EXCLUSIVE));
+
+	worker->hdr.in_use = false;
+	worker->hdr.proc = NULL;
+	worker->last_update_time = 0;
+}
+
+/*
+ * Attach slot sync worker to SlotSyncWorkerInfo assigned by the launcher.
  */
 void
-ApplyLauncherMain(Datum main_arg)
+slotsync_worker_attach()
 {
-	ereport(DEBUG1,
-			(errmsg_internal("logical replication launcher started")));
+	/* Block concurrent access. */
+	LWLockAcquire(SlotSyncWorkerLock, LW_EXCLUSIVE);
+
+	SlotSyncWorker = &LogicalRepCtx->ss_worker;
+
+	if (!SlotSyncWorker->hdr.in_use)
+	{
+		LWLockRelease(SlotSyncWorkerLock);
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("replication slot sync worker not initialized, cannot attach")));
+	}
+
+	if (SlotSyncWorker->hdr.proc)
+	{
+		LWLockRelease(SlotSyncWorkerLock);
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("replication slot sync worker is already running, cannot attach")));
+	}
+
+	SlotSyncWorker->hdr.proc = MyProc;
+
+	before_shmem_exit(slotsync_worker_detach, (Datum) 0);
+
+	LWLockRelease(SlotSyncWorkerLock);
+}
+
+/*
+ * Detach the slot sync worker (cleans up the worker info).
+ */
+void
+slotsync_worker_detach(int code, Datum arg)
+{
+	LWLockAcquire(SlotSyncWorkerLock, LW_EXCLUSIVE);
+	slotsync_worker_cleanup(SlotSyncWorker);
+	LWLockRelease(SlotSyncWorkerLock);
+}
+
+/*
+ * Start slot sync background worker.
+ *
+ * Returns true on success, false on failure.
+ */
+static bool
+slotsync_worker_launch()
+{
+	BackgroundWorker bgw;
+	BackgroundWorkerHandle *bgw_handle;
+	SlotSyncWorkerInfo *worker;
+	bool		attach;
+	uint16		generation;
+
+	/* The shared memory must only be modified under lock. */
+	LWLockAcquire(SlotSyncWorkerLock, LW_EXCLUSIVE);
+
+	SlotSyncWorker = &LogicalRepCtx->ss_worker;
+
+	worker = SlotSyncWorker;
+
+	/* Prepare the new worker. */
+	worker->hdr.in_use = true;
+
+	/* TODO: do we really need 'generation', analyse more here */
+	worker->hdr.generation++;
+
+	/*
+	 * 'proc' will be assigned in ReplSlotSyncWorkerMain when the worker
+	 * attaches to SlotSyncWorkerInfo.
+	 */
+	worker->hdr.proc = NULL;
+
+	/* Before releasing lock, remember generation for future identification. */
+	generation = worker->hdr.generation;
+
+	LWLockRelease(SlotSyncWorkerLock);
+
+	/* Register the new dynamic worker. */
+	memset(&bgw, 0, sizeof(bgw));
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState;
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+
+	snprintf(bgw.bgw_name, BGW_MAXLEN, "replication slot sync worker");
+
+	snprintf(bgw.bgw_type, BGW_MAXLEN, "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_NEVER_RESTART;
+	bgw.bgw_notify_pid = MyProcPid;
+
+	if (!RegisterDynamicBackgroundWorker(&bgw, &bgw_handle))
+	{
+		/* Failed to start worker, so clean up the worker slot. */
+		LWLockAcquire(SlotSyncWorkerLock, LW_EXCLUSIVE);
+		Assert(generation == worker->hdr.generation);
+		slotsync_worker_cleanup(worker);
+		LWLockRelease(SlotSyncWorkerLock);
+
+		ereport(WARNING,
+				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+				 errmsg("out of background worker slots"),
+				 errhint("You might need to increase %s.",
+						 "max_worker_processes")));
+		return false;
+	}
+
+	/* Now wait until it attaches. */
+	attach = WaitForReplicationWorkerAttach((LogicalWorkerHeader *) worker,
+											generation,
+											bgw_handle,
+											SlotSyncWorkerLock);
+
+	if (!attach)
+		ereport(WARNING,
+				(errmsg("replication slot sync worker failed to attach")));
+
+	return attach;
+}
+
+/*
+ * Internal function to stop the slot sync worker and cleanup afterwards.
+ */
+static void
+slotsync_worker_stop_internal(SlotSyncWorkerInfo *worker)
+{
+	LWLockAcquire(SlotSyncWorkerLock, LW_SHARED);
+	ereport(LOG,
+			(errmsg("stopping replication slot sync worker with pid: %d",
+					worker->hdr.proc->pid)));
+	logicalrep_worker_stop_internal((LogicalWorkerHeader *) worker,
+									SIGINT,
+									SlotSyncWorkerLock);
+	LWLockRelease(SlotSyncWorkerLock);
+}
+
+/*
+ * Validate if db with given dbname exists.
+ *
+ * Can't use existing functions like 'get_database_oid' from dbcommands.c for
+ * validity purpose as they need db connection.
+ */
+static bool
+validate_dbname(const char *dbname)
+{
+	HeapTuple	tuple;
+	Relation	relation;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	bool		valid;
+
+	/* Start a transaction so we can access pg_database */
+	StartTransactionCommand();
+
+	/* Form a scan key */
+	ScanKeyInit(&key[0], Anum_pg_database_datname, BTEqualStrategyNumber,
+				F_NAMEEQ, CStringGetDatum(dbname));
+
+	/* No db connection, force heap scan */
+	relation = table_open(DatabaseRelationId, AccessShareLock);
+	scan = systable_beginscan(relation, DatabaseNameIndexId, false,
+							  NULL, 1, key);
+
+	tuple = systable_getnext(scan);
+
+	if (HeapTupleIsValid(tuple))
+		valid = true;
+	else
+		valid = false;
+
+	/* all done */
+	systable_endscan(scan);
+	table_close(relation, AccessShareLock);
+
+	CommitTransactionCommand();
+	return valid;
+}
+
+/*
+ * Checks if GUC are set appropriately before starting slot sync worker
+ */
+static bool
+slotsync_checks(long *wait_time, bool *retry)
+{
+	char	   *dbname;
+
+	*retry = false;
+
+	if (!enable_syncslot)
+		return false;
+
+	/*
+	 * Since the above GUC is set, check that other GUC settings
+	 * (primary_slot_name, hot_standby_feedback, primary_conninfo, wal_level)
+	 * are compatible with slot synchronization. If not, issue warnings.
+	 */
+
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (!WalRcv || WalRcv->slotname[0] == '\0')
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronization as primary_slot_name is not set"));
+
+		/*
+		 * It's possible that the Walreceiver has not been started yet, adjust
+		 * the wait_time to retry sooner in the next synchronization cycle.
+		 */
+		*wait_time = wal_retrieve_retry_interval;
+
+		/*
+		 * Tell caller to retry the connection for the case where
+		 * primary_slot_name is set but Walreceiver is not yet started.
+		 */
+		if (PrimarySlotName && strcmp(PrimarySlotName, "") != 0)
+			*retry = true;
+
+		return false;
+	}
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronization as hot_standby_feedback is off"));
+		return false;
+	}
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronisation as it requires wal_level >= logical"));
+		return false;
+	}
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (dbname == NULL)
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronization as dbname is not specified in primary_conninfo"));
+		return false;
+	}
+
+	if (!validate_dbname(dbname))
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronization as dbname specified in primary_conninfo is not a valid one"));
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	if (LogicalRepCtx->ss_worker.hdr.in_use)
+		slotsync_worker_stop_internal(&LogicalRepCtx->ss_worker);
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If one of the slot sync options has changed, stop the slot sync worker
+ * and set ss_recheck flag to enable the caller to recheck slot sync GUCs
+ * before restarting the worker
+ */
+static void
+LauncherRereadConfig(bool *ss_recheck)
+{
+	char	   *conninfo = pstrdup(PrimaryConnInfo);
+	char	   *slotname = pstrdup(PrimarySlotName);
+	bool		syncslot = enable_syncslot;
+	bool		standbyfeedback = hot_standby_feedback;
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	/*
+	 * If any of the related GUCs changed, stop the slot sync worker. The
+	 * worker will be relaunched in next sync-cycle using the new GUCs.
+	 */
+	if ((strcmp(conninfo, PrimaryConnInfo) != 0) ||
+		(strcmp(slotname, PrimarySlotName) != 0) ||
+		(syncslot != enable_syncslot) ||
+		(standbyfeedback != hot_standby_feedback))
+	{
+		ShutDownSlotSync();
+
+		/* Retry slot sync with new GUCs */
+		*ss_recheck = true;
+	}
+
+	pfree(conninfo);
+	pfree(slotname);
+}
+
+/*
+ * Launch slot sync background worker.
+ */
+static void
+LaunchSlotSyncWorker(long *wait_time)
+{
+	LWLockAcquire(SlotSyncWorkerLock, LW_SHARED);
+
+	/* The worker is running already */
+	if (SlotSyncWorker && SlotSyncWorker->hdr.in_use &&
+		SlotSyncWorker->hdr.proc)
+	{
+		LWLockRelease(SlotSyncWorkerLock);
+		return;
+	}
+
+	LWLockRelease(SlotSyncWorkerLock);
+
+	/*
+	 * If launch failed, adjust the wait_time to retry in the next sync-cycle
+	 * sooner.
+	 */
+	if (!slotsync_worker_launch())
+	{
+		*wait_time = Min(*wait_time, wal_retrieve_retry_interval);
+	}
+}
+
+/*
+ * Launch logical replication apply workers for enabled subscriptions.
+ */
+static void
+LaunchSubscriptionApplyWorker(long *wait_time)
+{
+	List	   *sublist;
+	ListCell   *lc;
+	MemoryContext subctx;
+	MemoryContext oldctx;
+
+	/* Use temporary context to avoid leaking memory across cycles. */
+	subctx = AllocSetContextCreate(TopMemoryContext,
+								   "Logical Replication Launcher sublist",
+								   ALLOCSET_DEFAULT_SIZES);
+	oldctx = MemoryContextSwitchTo(subctx);
+
+	/* Start any missing workers for enabled subscriptions. */
+	sublist = get_subscription_list();
+	foreach(lc, sublist)
+	{
+		Subscription *sub = (Subscription *) lfirst(lc);
+		LogicalRepWorker *w;
+		TimestampTz last_start;
+		TimestampTz now;
+		long		elapsed;
+
+		if (!sub->enabled)
+			continue;
+
+		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		w = logicalrep_worker_find(sub->oid, InvalidOid, false);
+		LWLockRelease(LogicalRepWorkerLock);
+
+		if (w != NULL)
+			continue;			/* worker is running already */
+
+		/*
+		 * If the worker is eligible to start now, launch it.  Otherwise,
+		 * adjust wait_time so that we'll wake up as soon as it can be
+		 * started.
+		 *
+		 * Each subscription's apply worker can only be restarted once per
+		 * wal_retrieve_retry_interval, so that errors do not cause us to
+		 * repeatedly restart the worker as fast as possible.  In cases where
+		 * a restart is expected (e.g., subscription parameter changes),
+		 * another process should remove the last-start entry for the
+		 * subscription so that the worker can be restarted without waiting
+		 * for wal_retrieve_retry_interval to elapse.
+		 */
+		last_start = ApplyLauncherGetWorkerStartTime(sub->oid);
+		now = GetCurrentTimestamp();
+		if (last_start == 0 ||
+			(elapsed = TimestampDifferenceMilliseconds(last_start, now)) >=
+			wal_retrieve_retry_interval)
+		{
+			ApplyLauncherSetWorkerStartTime(sub->oid, now);
+			logicalrep_worker_launch(WORKERTYPE_APPLY,
+									 sub->dbid, sub->oid, sub->name,
+									 sub->owner, InvalidOid,
+									 DSM_HANDLE_INVALID);
+		}
+		else
+		{
+			*wait_time = Min(*wait_time,
+							 wal_retrieve_retry_interval - elapsed);
+		}
+	}
+
+	/* Switch back to original memory context. */
+	MemoryContextSwitchTo(oldctx);
+	/* Clean the temporary memory. */
+	MemoryContextDelete(subctx);
+}
+
+/*
+ * Main loop for the launcher process.
+ */
+void
+LauncherMain(Datum main_arg)
+{
+	bool		start_slotsync = false;
+	bool		recheck_slotsync = true;
+
+	elog(DEBUG1, "logical replication launcher started");
 
 	before_shmem_exit(logicalrep_launcher_onexit, (Datum) 0);
 
@@ -1139,79 +1623,32 @@ ApplyLauncherMain(Datum main_arg)
 	 */
 	BackgroundWorkerInitializeConnection(NULL, NULL, 0);
 
+	load_file("libpqwalreceiver", false);
+
 	/* Enter main loop */
 	for (;;)
 	{
 		int			rc;
-		List	   *sublist;
-		ListCell   *lc;
-		MemoryContext subctx;
-		MemoryContext oldctx;
 		long		wait_time = DEFAULT_NAPTIME_PER_CYCLE;
 
 		CHECK_FOR_INTERRUPTS();
 
-		/* Use temporary context to avoid leaking memory across cycles. */
-		subctx = AllocSetContextCreate(TopMemoryContext,
-									   "Logical Replication Launcher sublist",
-									   ALLOCSET_DEFAULT_SIZES);
-		oldctx = MemoryContextSwitchTo(subctx);
-
-		/* Start any missing workers for enabled subscriptions. */
-		sublist = get_subscription_list();
-		foreach(lc, sublist)
+		/*
+		 * If it is Hot standby, then try to launch slot sync worker else
+		 * launch apply workers.
+		 */
+		if (RecoveryInProgress() && !PromoteIsTriggered())
 		{
-			Subscription *sub = (Subscription *) lfirst(lc);
-			LogicalRepWorker *w;
-			TimestampTz last_start;
-			TimestampTz now;
-			long		elapsed;
-
-			if (!sub->enabled)
-				continue;
-
-			LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
-			w = logicalrep_worker_find(sub->oid, InvalidOid, false);
-			LWLockRelease(LogicalRepWorkerLock);
+			/* Make validation checks first */
+			if (recheck_slotsync)
+				start_slotsync = slotsync_checks(&wait_time, &recheck_slotsync);
 
-			if (w != NULL)
-				continue;		/* worker is running already */
-
-			/*
-			 * If the worker is eligible to start now, launch it.  Otherwise,
-			 * adjust wait_time so that we'll wake up as soon as it can be
-			 * started.
-			 *
-			 * Each subscription's apply worker can only be restarted once per
-			 * wal_retrieve_retry_interval, so that errors do not cause us to
-			 * repeatedly restart the worker as fast as possible.  In cases
-			 * where a restart is expected (e.g., subscription parameter
-			 * changes), another process should remove the last-start entry
-			 * for the subscription so that the worker can be restarted
-			 * without waiting for wal_retrieve_retry_interval to elapse.
-			 */
-			last_start = ApplyLauncherGetWorkerStartTime(sub->oid);
-			now = GetCurrentTimestamp();
-			if (last_start == 0 ||
-				(elapsed = TimestampDifferenceMilliseconds(last_start, now)) >= wal_retrieve_retry_interval)
-			{
-				ApplyLauncherSetWorkerStartTime(sub->oid, now);
-				logicalrep_worker_launch(WORKERTYPE_APPLY,
-										 sub->dbid, sub->oid, sub->name,
-										 sub->owner, InvalidOid,
-										 DSM_HANDLE_INVALID);
-			}
-			else
-			{
-				wait_time = Min(wait_time,
-								wal_retrieve_retry_interval - elapsed);
-			}
+			/* Start slot sync workers if checks passed */
+			if (start_slotsync)
+				LaunchSlotSyncWorker(&wait_time);
 		}
-
-		/* Switch back to original memory context. */
-		MemoryContextSwitchTo(oldctx);
-		/* Clean the temporary memory. */
-		MemoryContextDelete(subctx);
+		else
+			LaunchSubscriptionApplyWorker(&wait_time);
 
 		/* Wait for more work. */
 		rc = WaitLatch(MyLatch,
@@ -1226,10 +1663,7 @@ ApplyLauncherMain(Datum main_arg)
 		}
 
 		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
-		}
+			LauncherRereadConfig(&recheck_slotsync);
 	}
 
 	/* Not reachable */
@@ -1260,7 +1694,8 @@ GetLeaderApplyWorkerPid(pid_t pid)
 	{
 		LogicalRepWorker *w = &LogicalRepCtx->workers[i];
 
-		if (isParallelApplyWorker(w) && w->proc && pid == w->proc->pid)
+		if (isParallelApplyWorker(w) && w->hdr.proc &&
+			pid == w->hdr.proc->pid)
 		{
 			leader_pid = w->leader_pid;
 			break;
@@ -1298,13 +1733,13 @@ pg_stat_get_subscription(PG_FUNCTION_ARGS)
 
 		memcpy(&worker, &LogicalRepCtx->workers[i],
 			   sizeof(LogicalRepWorker));
-		if (!worker.proc || !IsBackendPid(worker.proc->pid))
+		if (!worker.hdr.proc || !IsBackendPid(worker.hdr.proc->pid))
 			continue;
 
 		if (OidIsValid(subid) && worker.subid != subid)
 			continue;
 
-		worker_pid = worker.proc->pid;
+		worker_pid = worker.hdr.proc->pid;
 
 		values[0] = ObjectIdGetDatum(worker.subid);
 		if (isTablesyncWorker(&worker))
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..5bef0138b4 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,28 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	/*
+	 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+	 * promotion.
+	 */
+	if (!RecoveryInProgress() && slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+		elog(ERROR, "replication slot \"%s\" was not synced completely from the primary server",
+			 NameStr(slot->data.name));
+
+	/*
+	 * Do not allow consumption of a "synchronized" slot until the standby
+	 * gets promoted. Also do not allow consumption of slots with sync_state
+	 * as SYNCSLOT_STATE_INITIATED as they are not synced completely to be
+	 * used.
+	 */
+	if (RecoveryInProgress() && slot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot use replication slot \"%s\" for logical decoding",
+						NameStr(slot->data.name)),
+				 errdetail("This slot is being synced from the primary server."),
+				 errhint("Specify another replication slot.")));
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..61bbf42933
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1032 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * It also takes care of dropping the slots which were created by it and are
+ * currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		conflicting;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+/*
+ * Number of attempts for wait_for_primary_slot_catchup() after
+ * which it aborts the wait and the slot sync worker then moves
+ * to the next slot creation/sync.
+ */
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * *persist will be set to false if the slot has disappeared or was invalidated
+ * on the primary; otherwise, it will be set to true.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *persist)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+	*persist = true;
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn, confirmed_flush_lsn,"
+					 " catalog_xmin FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		XLogRecPtr	new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *slot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		Assert(RecoveryInProgress());
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot info for slot \"%s\" from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" disappeared from the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			*persist = false;
+
+			return false;
+		}
+
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(slot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" invalidated on the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			ExecClearTuple(slot);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * Once we got valid restart_lsn, then confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(slot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\" and moving"
+						   " to the next slot, will attempt creating it again",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Wait for remote slot to pass locally reserved position and
+ * sync the slot locally if wait is over.
+ */
+static void
+wait_for_primary_and_sync(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+						  bool *slot_updated)
+{
+	/*
+	 * If the local restart_lsn and/or local catalog_xmin is ahead of those on
+	 * the remote then we cannot create the local slot in sync with the
+	 * primary server because that would mean moving the local slot backwards
+	 * and we might not have WALs retained for old LSN. In this case we will
+	 * wait for the primary server's restart_lsn and catalog_xmin to catch up
+	 * with the local one before attempting the sync.
+	 */
+	if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+		TransactionIdPrecedes(remote_slot->catalog_xmin,
+							  MyReplicationSlot->data.catalog_xmin))
+	{
+		bool		persist;
+
+		if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &persist))
+		{
+			/*
+			 * The remote slot didn't catch up to locally reserved position.
+			 *
+			 * We do not drop the slot because the restart_lsn can be ahead of
+			 * the current location when recreating the slot in the next cycle.
+			 * It may take more time to create such a slot. Therefore, we
+			 * persist it and attempt the wait and synchronization in the next
+			 * cycle.
+			 */
+			if (persist && MyReplicationSlot->data.persistency != RS_PERSISTENT)
+			{
+				ReplicationSlotPersist();
+				*slot_updated = true;
+			}
+
+			return;
+		}
+	}
+
+	/* Update LSN of slot to remote slot's current position */
+	local_slot_update(remote_slot);
+
+	if (MyReplicationSlot->data.persistency != RS_PERSISTENT)
+		ReplicationSlotPersist();
+	else
+		ReplicationSlotSave();
+
+	/*
+	 * Wait for primary is over, mark the slot as READY for incremental syncs.
+	 * Cascading standbys can also start syncing it.
+	 */
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	*slot_updated = true;
+
+	ereport(LOG, errmsg("newly locally created slot \"%s\" has been synced",
+						remote_slot->name));
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up.
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			slots = lappend(slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(slots);
+}
+
+/*
+ * Get list of local logical slot names which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slot_names(void)
+{
+	List	   *localSyncedSlots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			localSyncedSlots = lappend(localSyncedSlots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return localSyncedSlots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+slot_exists_in_list(ReplicationSlot *local_slot, List *remote_slots,
+					bool *locally_invalidated)
+{
+	ListCell   *cell;
+
+	foreach(cell, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * if remote slot is marked as non-conflicting (i.e. not
+			 * invalidated) but local slot is marked as invalidated, then set
+			 * the bool.
+			 */
+			*locally_invalidated =
+				!remote_slot->conflicting &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * This gets invalidation cause of the remote slot.
+ */
+static ReplicationSlotInvalidationCause
+get_remote_invalidation_cause(WalReceiverConn *wrconn, char *slot_name)
+{
+	WalRcvExecResult *res;
+	Oid			slotRow[1] = {INT2OID};
+	StringInfoData cmd;
+	bool		isnull;
+	TupleTableSlot *slot;
+	ReplicationSlotInvalidationCause cause;
+	MemoryContext oldctx = CurrentMemoryContext;
+
+	/* Syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	/* Make things live outside TX context */
+	MemoryContextSwitchTo(oldctx);
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT pg_get_slot_invalidation_cause(%s)",
+					 quote_literal_cstr(slot_name));
+	res = walrcv_exec(wrconn, cmd.data, 1, slotRow);
+	pfree(cmd.data);
+
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch invalidation cause for slot \"%s\" from"
+						" the primary server: %s", slot_name, res->err)));
+
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		ereport(ERROR,
+				(errmsg("slot \"%s\" disappeared from the primary server",
+						slot_name)));
+
+	cause = DatumGetInt16(slot_getattr(slot, 1, &isnull));
+	Assert(!isnull);
+
+	ExecClearTuple(slot);
+	walrcv_clear_result(res);
+
+	return cause;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Also drop the slots that are valid on the primary that got invalidated
+ * on the standby due to conflict (say required rows removed on the primary).
+ * The assumption is, that these will get recreated in next sync-cycle and
+ * it is okay to drop and recreate such slots as long as these are not
+ * consumable on the standby (which is the case currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slot_list = NIL;
+	ListCell   *lc_slot;
+
+	/*
+	 * Get the list of local 'synced' slot so that those not on remote could
+	 * be dropped.
+	 */
+	local_slot_list = get_local_synced_slot_names();
+
+	foreach(lc_slot, local_slot_list)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc_slot);
+		bool		local_exists = false;
+		bool		locally_invalidated = false;
+
+		local_exists = slot_exists_in_list(local_slot, remote_slot_list,
+										   &locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!local_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Constructs the query in order to get failover logical slots
+ * information from the primary server.
+ */
+static void
+construct_slot_query(StringInfo s)
+{
+	/*
+	 * Fetch data for logical failover slots with sync_state either as
+	 * SYNCSLOT_STATE_NONE or SYNCSLOT_STATE_READY.
+	 */
+	appendStringInfo(s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, conflicting, "
+					 " database FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover and sync_state != 'i'");
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ */
+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+					 bool *slot_updated)
+{
+	MemoryContext oldctx = CurrentMemoryContext;
+	ReplicationSlot *s;
+	char		sync_state = 0;
+
+	/*
+	 * Make sure that concerned WAL is received before syncing slot to target
+	 * lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot. But to
+	 * take care of any bug in that flow, we should retain this check.
+	 */
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		elog(LOG, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+
+		return;
+	}
+
+	/* Search for the named slot */
+	if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		SpinLockAcquire(&s->mutex);
+		sync_state = s->data.sync_state;
+		SpinLockRelease(&s->mutex);
+	}
+
+	StartTransactionCommand();
+
+	/* Make things live outside TX context */
+	MemoryContextSwitchTo(oldctx);
+
+	/*
+	 * Already existing slot (created by slot sync worker) and ready for sync,
+	 * acquire and sync it.
+	 */
+	if (sync_state == SYNCSLOT_STATE_READY)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&MyReplicationSlot->mutex);
+			MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&MyReplicationSlot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn)
+		{
+			ereport(WARNING,
+					errmsg("not synchronizing slot %s; synchronization would"
+						   " move it backwards", remote_slot->name));
+
+			goto cleanup;
+		}
+
+		if (remote_slot->confirmed_lsn != MyReplicationSlot->data.confirmed_flush ||
+			remote_slot->restart_lsn != MyReplicationSlot->data.restart_lsn ||
+			remote_slot->catalog_xmin != MyReplicationSlot->data.catalog_xmin)
+		{
+			/* Update LSN of slot to remote slot's current position */
+			local_slot_update(remote_slot);
+			ReplicationSlotSave();
+			*slot_updated = true;
+		}
+	}
+
+	/*
+	 * Already existing slot but not ready (i.e. waiting for the primary
+	 * server to catch-up), lets attempt to finish the first sync now.
+	 */
+	else if (sync_state == SYNCSLOT_STATE_INITIATED)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		wait_for_primary_and_sync(wrconn, remote_slot, slot_updated);
+	}
+	/* User created slot with the same name exists, raise ERROR. */
+	else if (sync_state == SYNCSLOT_STATE_NONE)
+	{
+		ereport(ERROR,
+				errmsg("not synchronizing slot %s; it is a user created slot",
+					   remote_slot->name));
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+		ReplicationSlot *slot;
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase, false);
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+
+		/* Mark it as sync initiated by slot sync worker */
+		slot->data.sync_state = SYNCSLOT_STATE_INITIATED;
+		slot->data.failover = true;
+
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		wait_for_primary_and_sync(wrconn, remote_slot, slot_updated);
+
+	}
+
+cleanup:
+
+	ReplicationSlotRelease();
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	return;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and update
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns nap time for the next sync-cycle.
+ */
+static long
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 8
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *slot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	MemoryContext oldctx = CurrentMemoryContext;
+	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	ListCell   *cell;
+	bool		slot_updated = false;
+	TimestampTz now;
+
+	/* The primary_slot_name is not set yet or WALs not received yet */
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+		return naptime;
+
+	/* The syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	/* Make things live outside TX context */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct query to get slots info from the primary server */
+	initStringInfo(&s);
+	construct_slot_query(&s);
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(slot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+																	   5, &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(slot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->conflicting = DatumGetBool(slot_getattr(slot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(slot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		if (remote_slot->conflicting)
+			remote_slot->invalidated = get_remote_invalidation_cause(wrconn,
+																	 remote_slot->name);
+		else
+			remote_slot->invalidated = RS_INVAL_NONE;
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(slot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(cell, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		synchronize_one_slot(wrconn, remote_slot, &slot_updated);
+	}
+
+	now = GetCurrentTimestamp();
+
+	/*
+	 * If any of the slots get updated in this sync-cycle, retain default
+	 * naptime and update 'last_update_time' in slot sync worker. But if no
+	 * activity is observed in this sync-cycle, then increase naptime provided
+	 * inactivity time reaches threshold.
+	 */
+	if (slot_updated)
+		SlotSyncWorker->last_update_time = now;
+
+	else if (TimestampDifferenceExceeds(SlotSyncWorker->last_update_time,
+										now, WORKER_INACTIVITY_THRESHOLD_MS))
+		naptime = WORKER_INACTIVITY_NAPTIME_MS;
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	return naptime;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false, "slot sync", &err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If primary_conninfo has changed, reconnect to primary.
+ */
+static void
+slotsync_reread_config(WalReceiverConn **wrconn)
+{
+	char	   *conninfo = pstrdup(PrimaryConnInfo);
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	/* Reconnect if GUC primary_conninfo got changed */
+	if (strcmp(conninfo, PrimaryConnInfo) != 0)
+	{
+		if (*wrconn)
+			walrcv_disconnect(*wrconn);
+
+		*wrconn = remote_connect();
+	}
+
+	pfree(conninfo);
+}
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting"
+					   " down on receiving SIGINT"));
+
+		walrcv_disconnect(*wrconn);
+		proc_exit(0);
+	}
+
+
+	if (ConfigReloadPending)
+		slotsync_reread_config(wrconn);
+}
+
+/*
+ * The main loop of our worker process.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	slotsync_worker_attach();
+
+	/*
+	 * If the standby has been promoted, skip the slot synchronization process.
+	 *
+	 * Although the startup process stops all the slot sync workers on
+	 * promotion, the launcher may not have realized the promotion and could
+	 * start additional workers after that. Therefore, this check is still
+	 * necessary to prevent these additional workers from running.
+	 */
+	if (PromoteIsTriggered())
+		exit(0);
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	/*
+	 * Get the user provided dbname from the connection string, if dbname not
+	 * provided, skip sync.
+	 */
+	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (dbname == NULL)
+		proc_exit(0);
+
+	/*
+	 * Connect to the database specified by user in PrimaryConnInfo. We need a
+	 * database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname,
+										 NULL,
+										 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime;
+
+		ProcessSlotSyncInterrupts(&wrconn);
+
+		/* Check if got promoted */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Drop the slots for which sync is initiated but not yet
+			 * completed i.e. they are still waiting for the primary server to
+			 * catch up.
+			 */
+			slotsync_drop_initiated_slots();
+			ereport(LOG,
+					errmsg("exiting slot sync woker on promotion of standby"));
+			proc_exit(0);
+		}
+
+		naptime = synchronize_slots(wrconn);
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 1eaee4197b..73aacef25f 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "commands/subscriptioncmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
@@ -246,7 +247,7 @@ wait_for_worker_state_change(char expected_state)
 		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
 		worker = logicalrep_worker_find(MyLogicalRepWorker->subid,
 										InvalidOid, false);
-		if (worker && worker->proc)
+		if (worker && worker->hdr.proc)
 			logicalrep_worker_wakeup_ptr(worker);
 		LWLockRelease(LogicalRepWorkerLock);
 		if (!worker)
@@ -535,7 +536,7 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 				if (rstate->state == SUBREL_STATE_SYNCWAIT)
 				{
 					/* Signal the sync worker, as it may be waiting for us. */
-					if (syncworker->proc)
+					if (syncworker->hdr.proc)
 						logicalrep_worker_wakeup_ptr(syncworker);
 
 					/* Now safe to release the LWLock */
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index c96a1700cd..2c059dffca 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -325,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = SYNCSLOT_STATE_NONE;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -684,12 +686,26 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+	{
+		ReplicationSlotRelease();
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary.")));
+	}
+
 	ReplicationSlotDropAcquired();
 }
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index fb6e37d2c3..7b1e0c1552 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3e44228bde..edb3656cd2 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, false);
 }
 
 /*
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 315a78cda9..fd9e73a49b 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -190,6 +190,8 @@ static const char *const BuiltinTrancheNames[] = {
 	"LogicalRepLauncherDSA",
 	/* LWTRANCHE_LAUNCHER_HASH: */
 	"LogicalRepLauncherHash",
+	/* LWTRANCHE_SLOTSYNC_DSA: */
+	"SlotSyncWorkerDSA",
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index f72f2906ce..e62a3f1bc0 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -54,3 +54,4 @@ XactTruncationLock					44
 WrapLimitsVacuumLock				46
 NotifyQueueTailLock					47
 WaitEventExtensionLock				48
+SlotSyncWorkerLock					49
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index e5e7bb23f9..65a1e544c1 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -65,8 +65,11 @@
 #include "postmaster/syslogger.h"
 #include "postmaster/walwriter.h"
 #include "replication/logicallauncher.h"
+#include "replication/reorderbuffer.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/walreceiver.h"
+#include "replication/walsender.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2021,6 +2024,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_SIGHUP, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 998080e0e4..eaafdb3035 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -355,6 +355,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = on			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d906734750..6a8192ad83 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 214dc6c29e..75b4b2040d 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -17,6 +17,7 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"
 
 extern ObjectAddress CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										bool isTopLevel);
@@ -28,4 +29,7 @@ extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
 extern char defGetStreamingMode(DefElem *def);
 
+extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn,
+										char *slotname, bool missing_ok);
+
 #endif							/* SUBSCRIPTIONCMDS_H */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index bef8a7162e..8a5b374cea 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -33,6 +33,15 @@ typedef struct IdentifySystemCmd
 	NodeTag		type;
 } IdentifySystemCmd;
 
+/* -------------------------------
+ *		LIST_DBID_FOR_FAILOVER_SLOTS command
+ * -------------------------------
+ */
+typedef struct ListDBForFailoverSlotsCmd
+{
+	NodeTag		type;
+	List	   *slot_names;
+}			ListDBForFailoverSlotsCmd;
 
 /* ----------------------
  *		BASE_BACKUP command
diff --git a/src/include/replication/logicallauncher.h b/src/include/replication/logicallauncher.h
index a07c9cb311..02499a7e66 100644
--- a/src/include/replication/logicallauncher.h
+++ b/src/include/replication/logicallauncher.h
@@ -15,9 +15,11 @@
 extern PGDLLIMPORT int max_logical_replication_workers;
 extern PGDLLIMPORT int max_sync_workers_per_subscription;
 extern PGDLLIMPORT int max_parallel_apply_workers_per_subscription;
+extern PGDLLIMPORT bool enable_syncslot;
+
 
 extern void ApplyLauncherRegister(void);
-extern void ApplyLauncherMain(Datum main_arg);
+extern void LauncherMain(Datum main_arg);
 
 extern Size ApplyLauncherShmemSize(void);
 extern void ApplyLauncherShmemInit(void);
@@ -31,4 +33,9 @@ extern bool IsLogicalLauncher(void);
 
 extern pid_t GetLeaderApplyWorkerPid(pid_t pid);
 
+extern void ShutDownSlotSync(void);
+
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+
 #endif							/* LOGICALLAUNCHER_H */
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index bbd71d0b42..baad5a8f3a 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -19,6 +19,7 @@ extern PGDLLIMPORT volatile sig_atomic_t ParallelApplyMessagePending;
 extern void ApplyWorkerMain(Datum main_arg);
 extern void ParallelApplyWorkerMain(Datum main_arg);
 extern void TablesyncWorkerMain(Datum main_arg);
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
 
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index ca06e5b1ad..6c300da45a 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -15,7 +15,6 @@
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "storage/spin.h"
-#include "replication/walreceiver.h"
 
 /*
  * Behaviour of replication slots, upon release or crash.
@@ -52,6 +51,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +119,13 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Is this a slot created by a sync-slot worker?
+	 *
+	 * Relevant for logical slots on the physical standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)?
 	 * Only relevant for logical slots on the primary server.
@@ -227,7 +241,7 @@ extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
 								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
@@ -253,7 +267,6 @@ extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_l
 extern int	ReplicationSlotIndex(ReplicationSlot *slot);
 extern bool ReplicationSlotName(int index, Name name);
 extern void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot);
-extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok);
 
 extern void StartupReplicationSlots(void);
 extern void CheckPointReplicationSlots(bool is_shutdown);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 61bc8de72c..87e45c990e 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -20,6 +20,7 @@
 #include "pgtime.h"
 #include "port/atomics.h"
 #include "replication/logicalproto.h"
+#include "replication/slot.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/latch.h"
@@ -191,6 +192,14 @@ typedef struct
 	}			proto;
 } WalRcvStreamOptions;
 
+/*
+ * Failover logical slots data received from remote.
+ */
+typedef struct WalRcvFailoverSlotsData
+{
+	Oid			dboid;
+}			WalRcvFailoverSlotsData;
+
 struct WalReceiverConn;
 typedef struct WalReceiverConn WalReceiverConn;
 
@@ -280,6 +289,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -404,6 +428,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -429,6 +454,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 4378690ab0..4161de1019 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -1,7 +1,8 @@
 /*-------------------------------------------------------------------------
  *
  * worker_internal.h
- *	  Internal headers shared by logical replication workers.
+ *	  Internal headers shared by logical replication workers
+ *	  and slotsync workers.
  *
  * Portions Copyright (c) 2016-2023, PostgreSQL Global Development Group
  *
@@ -36,14 +37,9 @@ typedef enum LogicalRepWorkerType
 	WORKERTYPE_PARALLEL_APPLY,
 } LogicalRepWorkerType;
 
-typedef struct LogicalRepWorker
+/* Common data for Slotsync and LogicalRep workers */
+typedef struct LogicalWorkerHeader
 {
-	/* What type of worker is this? */
-	LogicalRepWorkerType type;
-
-	/* Time at which this worker was launched. */
-	TimestampTz launch_time;
-
 	/* Indicates if this slot is used or free. */
 	bool		in_use;
 
@@ -53,6 +49,19 @@ typedef struct LogicalRepWorker
 	/* Pointer to proc array. NULL if not running. */
 	PGPROC	   *proc;
 
+} LogicalWorkerHeader;
+
+/* Shared memory structure for logical replication workers. */
+typedef struct LogicalRepWorker
+{
+	LogicalWorkerHeader hdr;
+
+	/* Time at which this worker was launched. */
+	TimestampTz launch_time;
+
+	/* What type of worker is this? */
+	LogicalRepWorkerType type;
+
 	/* Database id to connect to. */
 	Oid			dbid;
 
@@ -96,6 +105,24 @@ typedef struct LogicalRepWorker
 	TimestampTz reply_time;
 } LogicalRepWorker;
 
+/*
+ * Shared memory structure for Slot-Sync worker. It is allocated by logical
+ * replication launcher and then read by each slot sync worker.
+ *
+ * It is protected by LWLock (SlotSyncWorkerLock). Each slot sync worker
+ * reading the structure needs to hold the lock in shared mode, whereas
+ * the logical replication launcher which updates it needs to hold the lock
+ * in exclusive mode.
+ */
+typedef struct SlotSyncWorkerInfo
+{
+	LogicalWorkerHeader hdr;
+
+	/* The last sync-cycle time when the worker updated any of the slots. */
+	TimestampTz last_update_time;
+
+}			SlotSyncWorkerInfo;
+
 /*
  * State of the transaction in parallel apply worker.
  *
@@ -234,12 +261,16 @@ extern PGDLLIMPORT struct WalReceiverConn *LogRepWorkerWalRcvConn;
 /* Worker and subscription objects. */
 extern PGDLLIMPORT Subscription *MySubscription;
 extern PGDLLIMPORT LogicalRepWorker *MyLogicalRepWorker;
+extern PGDLLIMPORT SlotSyncWorkerInfo *SlotSyncWorker;
 
 extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
 extern void logicalrep_worker_attach(int slot);
+extern void slotsync_worker_attach(void);
+extern void slotsync_worker_detach(int code, Datum arg);
+extern void slotsync_drop_initiated_slots(void);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
 extern List *logicalrep_workers_find(Oid subid, bool only_running);
@@ -328,9 +359,9 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
-#define isParallelApplyWorker(worker) ((worker)->in_use && \
+#define isParallelApplyWorker(worker) ((worker)->hdr.in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
-#define isTablesyncWorker(worker) ((worker)->in_use && \
+#define isTablesyncWorker(worker) ((worker)->hdr.in_use && \
 								   (worker)->type == WORKERTYPE_TABLESYNC)
 
 static inline bool
@@ -342,14 +373,14 @@ am_tablesync_worker(void)
 static inline bool
 am_leader_apply_worker(void)
 {
-	Assert(MyLogicalRepWorker->in_use);
+	Assert(MyLogicalRepWorker->hdr.in_use);
 	return (MyLogicalRepWorker->type == WORKERTYPE_APPLY);
 }
 
 static inline bool
 am_parallel_apply_worker(void)
 {
-	Assert(MyLogicalRepWorker->in_use);
+	Assert(MyLogicalRepWorker->hdr.in_use);
 	return isParallelApplyWorker(MyLogicalRepWorker);
 }
 
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index b038e599c0..0621ee70fc 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -207,6 +207,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_PGSTATS_DATA,
 	LWTRANCHE_LAUNCHER_DSA,
 	LWTRANCHE_LAUNCHER_HASH,
+	LWTRANCHE_SLOTSYNC_DSA,
 	LWTRANCHE_FIRST_USER_DEFINED,
 }			BuiltinTrancheIds;
 
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
index bff0f52a46..e7a00bde12 100644
--- a/src/test/recovery/t/050_verify_slot_order.pl
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -146,4 +146,131 @@ $result = $subscriber1->safe_psql('postgres',
 	"SELECT count(*) = $primary_row_count FROM tab_int;");
 is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
 
+# Test logical failover slots on the standby
+# Configure standby3 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub1_slot->| ----> subscriber1 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb3_slot--->| ----> standby3 (connected via streaming replication)
+#                                        |                 lsub1_slot(synced_slot)
+
+# Cleanup old standby_slot_names
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = ''
+));
+$primary->start;
+
+$primary->psql('postgres',
+		q{SELECT pg_create_physical_replication_slot('sb3_slot');});
+
+$backup_name = 'backup2';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+my $connstr_1 = $primary->connstr;
+$standby3->stop;
+$standby3->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb3_slot'
+});
+$standby3->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+$standby3->start;
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb3_slot'
+));
+$primary->start;
+
+# Restart the standby
+$standby3->restart;
+
+# Wait for the standby to start sync
+my $offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots;}
+  ),
+  'lsub1_slot',
+  'failover slot was created');
+
+# Verify slot properties on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|r",
+  'logical slot has sync_state as ready and failover as true on standby');
+
+# Verify slot properties on the primary
+is( $primary->safe_psql('postgres',
+    q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|n",
+  'logical slot has sync_state as none and failover as true on primary');
+
+# Test to confirm that restart_lsn of the logical slot on the primary is synced to the standby
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_int;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# let the slots get synced on the standby
+sleep 2;
+
+# Get the restart_lsn for the logical slot lsub1_slot on the primary
+my $primary_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that restart_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'restart_lsn of slot lsub1_slot synced to standby');
+
+# Get the confirmed_flush_lsn for the logical slot lsub1_slot on the primary
+$primary_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that confirmed_flush_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub1_slot synced to the standby');
+
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d80d30e99c..b559974879 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1432,6 +1432,7 @@ LimitState
 LimitStateCond
 List
 ListCell
+ListDBForLogicalSlotsCmd
 ListDictionary
 ListParsedLex
 ListenAction
@@ -1511,6 +1512,7 @@ LogicalSlotInfo
 LogicalSlotInfoArr
 LogicalTape
 LogicalTapeSet
+LogicalWorkerHeader
 LsnReadQueue
 LsnReadQueueNextFun
 LsnReadQueueNextStatus
@@ -2314,6 +2316,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2572,6 +2575,8 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorker
+SlotSyncWorkerInfo
 SlruCtl
 SlruCtlData
 SlruErrorCause
@@ -3019,6 +3024,7 @@ WalLevel
 WalRcvData
 WalRcvExecResult
 WalRcvExecStatus
+WalRcvFailoverSlotsData
 WalRcvState
 WalRcvStreamOptions
 WalRcvWakeupReason
-- 
2.30.0.windows.2



  [application/octet-stream] v38-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (134.3K, ../../OS0PR01MB571646B8186F6A06404BD50194BDA@OS0PR01MB5716.jpnprd01.prod.outlook.com/4-v38-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From e2215727111f68d9adc05dcf1601fc2a7ec963f2 Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Tue, 21 Nov 2023 14:50:21 +0530
Subject: [PATCH v38 1/3] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to the
physical standbys so that logical replication can be resumed after failover. It
is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API. Examples:

CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

(failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of pg_replication_slots
view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).

A new walreceiver API walrcv_alter_slot has been introduced to
enable the failover of the slot on publisher node.
---
 contrib/test_decoding/expected/slot.out       |  58 ++++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |   9 +-
 doc/src/sgml/ref/create_subscription.sgml     |  23 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       |  91 ++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  44 ++-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  51 ++-
 src/backend/replication/logical/worker.c      |  72 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 182 +++++++++-
 src/backend/replication/slotfuncs.c           |  19 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 327 +++++++++++++++++-
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_upgrade/info.c                     |   6 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 src/test/recovery/t/050_verify_slot_order.pl  | 149 ++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 +++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 48 files changed, 1334 insertions(+), 163 deletions(-)
 create mode 100644 src/test/recovery/t/050_verify_slot_order.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 93735e3aea..6440378d5c 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4344,6 +4344,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..056404af5d 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..fa6cd1c43f 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,29 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..c3187e7b9a 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,29 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If only the slot_name is specified (without create_slot option),
+			 * it is possible that the user intends to use an existing slot on
+			 * the publisher, so here we enable failover for the slot if
+			 * requested.
+			 */
+			else if (opts.slot_name && failover_enabled)
+			{
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("enabled failover for replication slot \"%s\" on publisher",
+								opts.slot_name)));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,8 +1326,8 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
@@ -1288,6 +1335,12 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when failover is enabled"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
 
 					/* Make sure refresh sees the new list of publications. */
@@ -1334,8 +1387,8 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
@@ -1347,6 +1400,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
 
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when failover is enabled"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
 
 					/* Refresh the new list of publications. */
@@ -1392,6 +1455,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when failover is enabled"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
 
 				AlterSubscription_refresh(sub, opts.copy_data, NULL);
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 60d5c1fc40..336c2bec99 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -883,8 +887,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -913,7 +917,14 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+		{
+			appendStringInfoString(&cmd, "FAILOVER");
+			if (use_new_options_syntax)
+				appendStringInfoString(&cmd, ", ");
+			else
+				appendStringInfoChar(&cmd, ' ');
+		}
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -982,6 +993,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					 bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index df3c42eb5d..1eaee4197b 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -614,15 +614,26 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
+									MySubscription->name)));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that failover can be enabled",
+									MySubscription->name)));
+
 				should_exit = true;
 			}
 		}
@@ -1420,7 +1431,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1722,10 +1734,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1733,9 +1747,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1749,9 +1762,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..0b7d632922 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,9 +132,41 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * the failover = true when creating the subscription. Enabling failover allows
+ * us to smoothly transition to the standby in case the primary gets promoted,
+ * ensuring that we can subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby. During syncing, the local restart_lsn and/or local catalog_xmin of
+ * the newly created slot on the standby are typically ahead of those on the
+ * primary. Therefore, the standby needs to wait for the primary server's
+ * restart_lsn and catalog_xmin to catch up, which takes time.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
+
 #include "postgres.h"
 
 #include <sys/stat.h>
@@ -3947,6 +3979,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4515,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4573,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4613,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..c96a1700cd 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List	   *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -251,7 +263,8 @@ ReplicationSlotValidateName(const char *name, int elevel)
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +324,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +693,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2198,140 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+
+	/* Need a modifiable copy of string */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into list of identifiers */
+	if (!SplitIdentifierString(rawname, ',', &elemlist))
+	{
+		/* syntax error in name list */
+		GUC_check_errdetail("List syntax is invalid.");
+		goto ret_standby_slot_names_ng;
+	}
+
+	/*
+	 * Verify 'type' of slot now.
+	 *
+	 * Skip check if replication slots' data is not initialized yet i.e. we
+	 * are in startup process.
+	 */
+	if (!ReplicationSlotCtl)
+		goto ret_standby_slot_names_ok;
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+			goto ret_standby_slot_names_ng;
+
+		if (SlotIsLogical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			goto ret_standby_slot_names_ng;
+		}
+	}
+
+ret_standby_slot_names_ok:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return true;
+
+ret_standby_slot_names_ng:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return false;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..fb6e37d2c3 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -679,6 +689,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +745,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +785,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 2398167f49..e27d231174 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -386,7 +386,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..3e44228bde 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool	failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,233 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up logical walsenders with failover-enabled slots if the physical slot
+ * of the current walsender is specified in standby_slot_names GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
+		bool		invalidated = false;
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (slot && SlotIsPhysical(slot))
+		{
+			SpinLockAcquire(&slot->mutex);
+			restart_lsn = slot->data.restart_lsn;
+			invalidated = slot->data.invalidated != RS_INVAL_NONE;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Continue if the current slot hasn't caught up. */
+		if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
+			restart_lsn < wait_for_lsn)
+		{
+			/* Log warning if no active_pid for this physical slot */
+			if (slot->active_pid == 0)
+				ereport(WARNING,
+						errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+							   name, "standby_slot_names"),
+						errdetail("Logical replication is waiting on the "
+								  "standby associated with \"%s\"", name),
+						errhint("Consider starting standby associated with "
+								"\"%s\" or amend standby_slot_names", name));
+
+			continue;
+		}
+
+		/*
+		 * It may happen that the slot specified in standby_slot_names GUC
+		 * value is dropped, so let's skip over it.
+		 */
+		else if (!slot)
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+
+		/*
+		 * If logical slot name is given in standby_slot_names, give WARNING
+		 * and skip it. Since it is harmless, so WARNING should be enough, no
+		 * need to error-out.
+		 */
+		else if (SlotIsLogical(slot))
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+
+		/*
+		 * Specified physical slot may have been invalidated, so no point in
+		 * waiting for it.
+		 */
+		else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
+			warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+		else
+			Assert(restart_lsn >= wait_for_lsn);
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, the
+ * function also waits for all the specified streaming replication standby
+ * servers to confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr if we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
 	if (RecentFlushPtr != InvalidXLogRecPtr &&
 		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know we
+		 * have enough WAL available and all the standby servers have confirmed
+		 * receipt of WAL up to RecentFlushPtr. This is particularly interesting
+		 * if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1825,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1840,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,8 +1879,14 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
 			break;
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
@@ -1654,9 +1927,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2094,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2331,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3594,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3665,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another CV
+	 * that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b764ef6998..e5e7bb23f9 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4562,6 +4562,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e48c066a5b..998080e0e4 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -326,6 +326,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..a348932e62 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,14 +679,17 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
+
 		slotinfos = (LogicalSlotInfo *) pg_malloc(sizeof(LogicalSlotInfo) * num_slots);
 
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +700,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..f6ac78418e 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced
+								 * to the physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 5b01cf8c40..0a1c467ed0 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -158,7 +158,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -172,8 +172,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 006e10f5d2..46e875c48b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3306,7 +3306,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb58dee3bc..d906734750 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..ca06e5b1ad 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)?
+	 * Only relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 04b439dc50..61bc8de72c 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -356,9 +356,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									   const char *slotname,
+									   bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -400,6 +411,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -429,8 +441,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 268f8e8d0f..1fcc22a127 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -14,6 +14,8 @@
 
 #include <signal.h>
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -47,6 +49,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 47854b5cd4..4378690ab0 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -258,7 +258,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..3be3ee52fc 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_verify_slot_order.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
new file mode 100644
index 0000000000..bff0f52a46
--- /dev/null
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -0,0 +1,149 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres', "CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+$subscriber1->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true
+$subscriber1->safe_psql('postgres',
+		"CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);");
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+$subscriber2->safe_psql('postgres',
+		"CREATE SUBSCRIPTION mysub2 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);");
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby comes
+# up.
+$standby1->stop;
+
+# Create some data on primary
+my $primary_row_count = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = 0 FROM tab_int;");
+is($result, 't', "subscriber1 doesn't get data from primary until standby1 acknowledges changes");
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 86a9886d4f..d80d30e99c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3862,6 +3863,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.30.0.windows.2



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-11-27 06:02   ` Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-11-27 06:02 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Monday, November 27, 2023 12:03 PM Zhijie Hou (Fujitsu) <[email protected]> wrote:
> 
> Attach the V38 patch set which addressed all comments in [1][2] except for the
> ones that mentioned above.
> 
> [1]
> https://www.postgresql.org/message-id/CAHut%2BPv-yu71ogj_hRi6cCtmD5
> 5bsyw7XTxj1Nq8yVFKpY3NDQ%40mail.gmail.com
> [2]
> https://www.postgresql.org/message-id/CAHut%2BPuEGX5kr0xh06yv8ndoA
> QvDNedoec1OqOq3GMxDN6p%3D9A%40mail.gmail.com

I didn't increment the patch version, sorry for that. Attach the same patch set
but increment the patch version to V39.

Best Regards,
Hou zj


Attachments:

  [application/octet-stream] v39-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch (8.0K, ../../OS0PR01MB57161C27BCFAF04C2B48B47394BDA@OS0PR01MB5716.jpnprd01.prod.outlook.com/2-v39-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch)
  download | inline diff:
From 2f884cf1f49fa55545f6190a03b37c2422009fc8 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Tue, 21 Nov 2023 12:12:23 +0800
Subject: [PATCH v39 3/3] Allow slot-sync worker to wait for the cascading
 standbys.

The GUC standby_slot_names is needed to be set on first standby
in order to allow it to wait for confirmation for cascading
standbys before updating logical 'synced' slots in slot-sync worker.
The intent is that the logical slots (synced ones) should not go
ahead of cascading standbys.

For the user created slots on first standby, we already have this wait
logic in place in logical walsender and in pg_logical_slot_get_changes_guts(),
but for synced slots (which can not be consumed yet), we need to make
sure that they are not going ahead of cascading standbys and that is
acheived by introducing the wait in slot-sync worker before we actually
update the slots.
---
 src/backend/replication/logical/slotsync.c | 86 ++++++++++++++++++++--
 src/backend/replication/walsender.c        | 11 +--
 src/include/replication/walsender.h        |  5 ++
 3 files changed, 89 insertions(+), 13 deletions(-)

diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 61bbf42933..6f5b85d6c7 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -83,6 +83,9 @@ typedef struct RemoteSlot
  */
 #define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
 
+static void ProcessSlotSyncInterrupts(WalReceiverConn **wrconn,
+									  List **standby_slots);
+
 /*
  * Wait for remote slot to pass locally reserved position.
  *
@@ -559,6 +562,52 @@ construct_slot_query(StringInfo s)
 					 " WHERE failover and sync_state != 'i'");
 }
 
+/*
+ * Wait for cascading physical standbys corresponding to physical slots
+ * specified in standby_slot_names GUC to confirm receiving given lsn.
+ */
+static void
+wait_for_standby_confirmation(XLogRecPtr wait_for_lsn,
+							  WalReceiverConn *wrconn)
+{
+	List	   *standby_slots;
+
+	/* Nothing to be done */
+	if (strcmp(standby_slot_names, "") == 0)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	for (;;)
+	{
+		int			rc;
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		/*
+		 * This will reload configuration and will refresh the standby_slots
+		 * as well provided standby_slot_names GUC is changed by the user.
+		 */
+		ProcessSlotSyncInterrupts(&wrconn, &standby_slots);
+
+		/*
+		 * XXX: Is waiting for 5 second before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   5000L,
+					   WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+}
+
 /*
  * Synchronize single slot to given position.
  *
@@ -743,6 +792,7 @@ synchronize_slots(WalReceiverConn *wrconn)
 	List	   *remote_slot_list = NIL;
 	MemoryContext oldctx = CurrentMemoryContext;
 	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	XLogRecPtr      max_confirmed_lsn = 0;
 	ListCell   *cell;
 	bool		slot_updated = false;
 	TimestampTz now;
@@ -792,6 +842,9 @@ synchronize_slots(WalReceiverConn *wrconn)
 		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
 		Assert(!isnull);
 
+		if (remote_slot->confirmed_lsn > max_confirmed_lsn)
+			max_confirmed_lsn = remote_slot->confirmed_lsn;
+
 		/*
 		 * It is possible to get null values for LSN and Xmin if slot is
 		 * invalidated on the primary server, so handle accordingly.
@@ -838,6 +891,17 @@ synchronize_slots(WalReceiverConn *wrconn)
 	 */
 	drop_obsolete_slots(remote_slot_list);
 
+	/*
+	 * If there are cascading standbys, wait for their confirmation before we
+	 * update synced logical slots locally.
+	 *
+	 * Instead of waiting on confirmation for lsn of each slot, let us wait
+	 * once for confirmation on max_confirmed_lsn. If that is confirmed by
+	 * each cascading standby, we are good to update all the slots.
+	 */
+	if (remote_slot_list)
+		wait_for_standby_confirmation(max_confirmed_lsn, wrconn);
+
 	/* Now sync the slots locally */
 	foreach(cell, remote_slot_list)
 	{
@@ -895,12 +959,18 @@ remote_connect(void)
  * If primary_conninfo has changed, reconnect to primary.
  */
 static void
-slotsync_reread_config(WalReceiverConn **wrconn)
+slotsync_reread_config(WalReceiverConn **wrconn, List **standby_slots)
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 
-	ConfigReloadPending = false;
-	ProcessConfigFile(PGC_SIGHUP);
+	/*
+	 * Reload configs and recreate the standby_slot_names_list if GUC
+	 * standby_slot_names changed.
+	 */
+	if (standby_slots)
+		WalSndRereadConfigAndReInitSlotList(standby_slots);
+	else
+		ProcessConfigFile(PGC_SIGHUP);
 
 	/* Reconnect if GUC primary_conninfo got changed */
 	if (strcmp(conninfo, PrimaryConnInfo) != 0)
@@ -918,7 +988,8 @@ slotsync_reread_config(WalReceiverConn **wrconn)
  * Interrupt handler for main loop of slot sync worker.
  */
 static void
-ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
+ProcessSlotSyncInterrupts(WalReceiverConn **wrconn,
+						  List **standby_slots)
 {
 	CHECK_FOR_INTERRUPTS();
 
@@ -934,7 +1005,10 @@ ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
 
 
 	if (ConfigReloadPending)
-		slotsync_reread_config(wrconn);
+	{
+		ConfigReloadPending = false;
+		slotsync_reread_config(wrconn, standby_slots);
+	}
 }
 
 /*
@@ -996,7 +1070,7 @@ ReplSlotSyncWorkerMain(Datum main_arg)
 		int			rc;
 		long		naptime;
 
-		ProcessSlotSyncInterrupts(&wrconn);
+		ProcessSlotSyncInterrupts(&wrconn, NULL /* standby_slots */ );
 
 		/* Check if got promoted */
 		if (!RecoveryInProgress())
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index edb3656cd2..db5af0b598 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1608,7 +1608,7 @@ PhysicalWakeupLogicalWalSnd(void)
  * Reload the config file and reinitialize the standby slot list if the GUC
  * standby_slot_names has changed.
  */
-static void
+void
 WalSndRereadConfigAndReInitSlotList(List **standby_slots)
 {
 	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
@@ -1631,13 +1631,12 @@ WalSndRereadConfigAndReInitSlotList(List **standby_slots)
  * This function updates the passed standby_slots list, removing any slots that
  * have already caught up to or surpassed the given wait_for_lsn.
  */
-static void
+void
 WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 {
 	ListCell   *lc;
-	List	   *standby_slots_cpy = *standby_slots;
 
-	foreach(lc, standby_slots_cpy)
+	foreach(lc, *standby_slots)
 	{
 		char	   *name = lfirst(lc);
 		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
@@ -1704,10 +1703,8 @@ WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 		if (warningfmt)
 			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
 
-		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+		*standby_slots = foreach_delete_current(*standby_slots, lc);
 	}
-
-	*standby_slots = standby_slots_cpy;
 }
 
 /*
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 1fcc22a127..c5c4714788 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -15,6 +15,7 @@
 #include <signal.h>
 
 #include "access/xlogdefs.h"
+#include "nodes/pg_list.h"
 
 /*
  * What to do with a snapshot in create replication slot command.
@@ -50,7 +51,11 @@ extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn,
+									 List **standby_slots);
 extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
+extern List *WalSndGetStandbySlots(void);
+extern void WalSndRereadConfigAndReInitSlotList(List **standby_slots);
 
 /*
  * Remember that we want to wakeup walsenders later
-- 
2.30.0.windows.2



  [application/octet-stream] v39-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (134.3K, ../../OS0PR01MB57161C27BCFAF04C2B48B47394BDA@OS0PR01MB5716.jpnprd01.prod.outlook.com/3-v39-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From 24dba0abff51672a3505e27adf36b395d7412f4d Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Tue, 21 Nov 2023 14:50:21 +0530
Subject: [PATCH v39 1/3] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to the
physical standbys so that logical replication can be resumed after failover. It
is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API. Examples:

CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

(failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of pg_replication_slots
view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).

A new walreceiver API walrcv_alter_slot has been introduced to
enable the failover of the slot on publisher node.
---
 contrib/test_decoding/expected/slot.out       |  58 ++++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |   9 +-
 doc/src/sgml/ref/create_subscription.sgml     |  23 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       |  91 ++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  44 ++-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  51 ++-
 src/backend/replication/logical/worker.c      |  72 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 182 +++++++++-
 src/backend/replication/slotfuncs.c           |  19 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 327 +++++++++++++++++-
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_upgrade/info.c                     |   6 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 src/test/recovery/t/050_verify_slot_order.pl  | 149 ++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 +++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 48 files changed, 1334 insertions(+), 163 deletions(-)
 create mode 100644 src/test/recovery/t/050_verify_slot_order.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 93735e3aea..6440378d5c 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4344,6 +4344,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..056404af5d 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..fa6cd1c43f 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,29 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..c3187e7b9a 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,29 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If only the slot_name is specified (without create_slot option),
+			 * it is possible that the user intends to use an existing slot on
+			 * the publisher, so here we enable failover for the slot if
+			 * requested.
+			 */
+			else if (opts.slot_name && failover_enabled)
+			{
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("enabled failover for replication slot \"%s\" on publisher",
+								opts.slot_name)));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,8 +1326,8 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
@@ -1288,6 +1335,12 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when failover is enabled"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
 
 					/* Make sure refresh sees the new list of publications. */
@@ -1334,8 +1387,8 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
@@ -1347,6 +1400,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
 
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when failover is enabled"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
 
 					/* Refresh the new list of publications. */
@@ -1392,6 +1455,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when failover is enabled"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
 
 				AlterSubscription_refresh(sub, opts.copy_data, NULL);
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 60d5c1fc40..336c2bec99 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -883,8 +887,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -913,7 +917,14 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+		{
+			appendStringInfoString(&cmd, "FAILOVER");
+			if (use_new_options_syntax)
+				appendStringInfoString(&cmd, ", ");
+			else
+				appendStringInfoChar(&cmd, ' ');
+		}
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -982,6 +993,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					 bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index df3c42eb5d..1eaee4197b 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -614,15 +614,26 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
+									MySubscription->name)));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that failover can be enabled",
+									MySubscription->name)));
+
 				should_exit = true;
 			}
 		}
@@ -1420,7 +1431,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1722,10 +1734,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1733,9 +1747,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1749,9 +1762,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..0b7d632922 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,9 +132,41 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * the failover = true when creating the subscription. Enabling failover allows
+ * us to smoothly transition to the standby in case the primary gets promoted,
+ * ensuring that we can subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby. During syncing, the local restart_lsn and/or local catalog_xmin of
+ * the newly created slot on the standby are typically ahead of those on the
+ * primary. Therefore, the standby needs to wait for the primary server's
+ * restart_lsn and catalog_xmin to catch up, which takes time.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
+
 #include "postgres.h"
 
 #include <sys/stat.h>
@@ -3947,6 +3979,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4515,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4573,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4613,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..c96a1700cd 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List	   *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -251,7 +263,8 @@ ReplicationSlotValidateName(const char *name, int elevel)
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +324,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +693,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2198,140 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+
+	/* Need a modifiable copy of string */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into list of identifiers */
+	if (!SplitIdentifierString(rawname, ',', &elemlist))
+	{
+		/* syntax error in name list */
+		GUC_check_errdetail("List syntax is invalid.");
+		goto ret_standby_slot_names_ng;
+	}
+
+	/*
+	 * Verify 'type' of slot now.
+	 *
+	 * Skip check if replication slots' data is not initialized yet i.e. we
+	 * are in startup process.
+	 */
+	if (!ReplicationSlotCtl)
+		goto ret_standby_slot_names_ok;
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+			goto ret_standby_slot_names_ng;
+
+		if (SlotIsLogical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			goto ret_standby_slot_names_ng;
+		}
+	}
+
+ret_standby_slot_names_ok:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return true;
+
+ret_standby_slot_names_ng:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return false;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..fb6e37d2c3 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -679,6 +689,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +745,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +785,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 2398167f49..e27d231174 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -386,7 +386,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..3e44228bde 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool	failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,233 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up logical walsenders with failover-enabled slots if the physical slot
+ * of the current walsender is specified in standby_slot_names GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
+		bool		invalidated = false;
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (slot && SlotIsPhysical(slot))
+		{
+			SpinLockAcquire(&slot->mutex);
+			restart_lsn = slot->data.restart_lsn;
+			invalidated = slot->data.invalidated != RS_INVAL_NONE;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Continue if the current slot hasn't caught up. */
+		if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
+			restart_lsn < wait_for_lsn)
+		{
+			/* Log warning if no active_pid for this physical slot */
+			if (slot->active_pid == 0)
+				ereport(WARNING,
+						errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+							   name, "standby_slot_names"),
+						errdetail("Logical replication is waiting on the "
+								  "standby associated with \"%s\"", name),
+						errhint("Consider starting standby associated with "
+								"\"%s\" or amend standby_slot_names", name));
+
+			continue;
+		}
+
+		/*
+		 * It may happen that the slot specified in standby_slot_names GUC
+		 * value is dropped, so let's skip over it.
+		 */
+		else if (!slot)
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+
+		/*
+		 * If logical slot name is given in standby_slot_names, give WARNING
+		 * and skip it. Since it is harmless, so WARNING should be enough, no
+		 * need to error-out.
+		 */
+		else if (SlotIsLogical(slot))
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+
+		/*
+		 * Specified physical slot may have been invalidated, so no point in
+		 * waiting for it.
+		 */
+		else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
+			warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+		else
+			Assert(restart_lsn >= wait_for_lsn);
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, the
+ * function also waits for all the specified streaming replication standby
+ * servers to confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr if we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
 	if (RecentFlushPtr != InvalidXLogRecPtr &&
 		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know we
+		 * have enough WAL available and all the standby servers have confirmed
+		 * receipt of WAL up to RecentFlushPtr. This is particularly interesting
+		 * if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1825,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1840,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,8 +1879,14 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
 			break;
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
@@ -1654,9 +1927,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2094,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2331,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3594,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3665,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another CV
+	 * that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b764ef6998..e5e7bb23f9 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4562,6 +4562,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e48c066a5b..998080e0e4 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -326,6 +326,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..a348932e62 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,14 +679,17 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
+
 		slotinfos = (LogicalSlotInfo *) pg_malloc(sizeof(LogicalSlotInfo) * num_slots);
 
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +700,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..f6ac78418e 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced
+								 * to the physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 5b01cf8c40..0a1c467ed0 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -158,7 +158,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -172,8 +172,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 006e10f5d2..46e875c48b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3306,7 +3306,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb58dee3bc..d906734750 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..ca06e5b1ad 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)?
+	 * Only relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 04b439dc50..61bc8de72c 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -356,9 +356,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									   const char *slotname,
+									   bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -400,6 +411,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -429,8 +441,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 268f8e8d0f..1fcc22a127 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -14,6 +14,8 @@
 
 #include <signal.h>
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -47,6 +49,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 47854b5cd4..4378690ab0 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -258,7 +258,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..3be3ee52fc 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_verify_slot_order.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
new file mode 100644
index 0000000000..bff0f52a46
--- /dev/null
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -0,0 +1,149 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres', "CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+$subscriber1->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true
+$subscriber1->safe_psql('postgres',
+		"CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);");
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+$subscriber2->safe_psql('postgres',
+		"CREATE SUBSCRIPTION mysub2 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);");
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby comes
+# up.
+$standby1->stop;
+
+# Create some data on primary
+my $primary_row_count = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = 0 FROM tab_int;");
+is($result, 't', "subscriber1 doesn't get data from primary until standby1 acknowledges changes");
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 86a9886d4f..d80d30e99c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3862,6 +3863,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.30.0.windows.2



  [application/octet-stream] v39-0002-Add-logical-slot-sync-capability-to-the-physical.patch (104.3K, ../../OS0PR01MB57161C27BCFAF04C2B48B47394BDA@OS0PR01MB5716.jpnprd01.prod.outlook.com/4-v39-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From 66f9a2092b138b0d3962e562fb7c89a77550fb8c Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Mon, 27 Nov 2023 11:36:09 +0800
Subject: [PATCH v39 2/3] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover. All the failover logical
replication slots on the primary (assuming configurations are
appropriate) are automatically created on the physical standbys and
are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The replication launcher on the physical standby starts slot-sync worker
which is then responsible to keep on syncing the logical failover slots
from the primary server.

The nap time of worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. And if
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated. If a logical slot on the primary is valid but is invalidated
on the standby due to conflict (say required rows removed on the primary),
then that slot is dropped and recreated on the standby in next sync-cycle.
It is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently).

Slots synced on the standby can be identified using 'sync_state' column of
pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but waiting for the remote slot on the
     primary server to catch up.
'r': ready for periodic syncs.
---
 doc/src/sgml/config.sgml                      |   35 +-
 doc/src/sgml/logicaldecoding.sgml             |   13 +
 doc/src/sgml/system-views.sgml                |   18 +
 src/backend/access/transam/xlogrecovery.c     |   11 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    5 +-
 .../libpqwalreceiver/libpqwalreceiver.c       |   44 +-
 src/backend/replication/logical/Makefile      |    1 +
 .../replication/logical/applyparallelworker.c |    3 +-
 src/backend/replication/logical/launcher.c    |  667 +++++++++--
 src/backend/replication/logical/logical.c     |   22 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1032 +++++++++++++++++
 src/backend/replication/logical/tablesync.c   |    5 +-
 src/backend/replication/slot.c                |   18 +-
 src/backend/replication/slotfuncs.c           |   33 +-
 src/backend/replication/walsender.c           |    2 +-
 src/backend/storage/lmgr/lwlock.c             |    2 +
 src/backend/storage/lmgr/lwlocknames.txt      |    1 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   12 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/commands/subscriptioncmds.h       |    4 +
 src/include/nodes/replnodes.h                 |    9 +
 src/include/replication/logicallauncher.h     |    9 +-
 src/include/replication/logicalworker.h       |    1 +
 src/include/replication/slot.h                |   19 +-
 src/include/replication/walreceiver.h         |   27 +
 src/include/replication/worker_internal.h     |   55 +-
 src/include/storage/lwlock.h                  |    1 +
 src/test/recovery/t/050_verify_slot_order.pl  |  127 ++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    6 +
 35 files changed, 2056 insertions(+), 151 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 6440378d5c..b2da901ad6 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4357,6 +4357,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must enable
+        <varname>enable_syncslot</varname> for the standbys to receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4550,10 +4556,15 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           A password needs to be provided too, if the sender demands password
           authentication.  It can be provided in the
           <varname>primary_conninfo</varname> string, or in a separate
-          <filename>~/.pgpass</filename> file on the standby server (use
+          <filename>~/.pgpass</filename> file on the standby server. (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          Specify <literal>dbname</literal> in
+          <varname>primary_conninfo</varname> string to allow synchronization
+          of slots from the primary server to the standby server.
+          This will only be used for slot synchronization. It is ignored
+          for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4878,6 +4889,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..90bec06f36 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,19 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and set
+     <varname>enable_syncslot</varname> on the standby. The physical replication
+     slot for the standby should be listed in
+     <varname>standby_slot_names</varname> on the primary to prevent the
+     subscriber from consuming changes faster than the hot standby.
+     Additionally, similar to creating a logical replication slot on the hot
+     standby, <varname>hot_standby_feedback</varname> should be set on the
+     standby and a physical slot between the primary and the standby should be
+     used.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..3631681adc 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,24 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on physical
+      standby which has enabled slots synchronization. For the primary server,
+      its value is always 'none'.
+      </para>
+      <para>
+       State code:
+       <literal>n</literal> = none for user created slots,
+       <literal>i</literal> = sync initiated for the slot but not yet completed,
+       not ready for periodic syncs,
+       <literal>r</literal> = ready for periodic syncs
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c61566666a..8ea6dc799a 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -49,7 +49,9 @@
 #include "postmaster/bgwriter.h"
 #include "postmaster/startup.h"
 #include "replication/slot.h"
+#include "replication/logicallauncher.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1437,15 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 48a9924527..0e039c786f 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -125,11 +125,14 @@ static const struct
 		"ParallelWorkerMain", ParallelWorkerMain
 	},
 	{
-		"ApplyLauncherMain", ApplyLauncherMain
+		"LauncherMain", LauncherMain
 	},
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 336c2bec99..5f82d01e6d 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -413,6 +416,45 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	PQconninfoOption *opt;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
@@ -998,7 +1040,7 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
  */
 static void
 libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
-					 bool failover)
+					bool failover)
 {
 	StringInfoData cmd;
 	PGresult   *res;
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 9b37736f8e..192c9e1860 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -922,7 +922,8 @@ ParallelApplyWorkerMain(Datum main_arg)
 	before_shmem_exit(pa_shutdown, PointerGetDatum(seg));
 
 	SpinLockAcquire(&MyParallelShared->mutex);
-	MyParallelShared->logicalrep_worker_generation = MyLogicalRepWorker->generation;
+	MyParallelShared->logicalrep_worker_generation =
+								MyLogicalRepWorker->hdr.generation;
 	MyParallelShared->logicalrep_worker_slot_no = worker_slot;
 	SpinLockRelease(&MyParallelShared->mutex);
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 501910b445..c0d6cf7e85 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -8,20 +8,27 @@
  *	  src/backend/replication/logical/launcher.c
  *
  * NOTES
- *	  This module contains the logical replication worker launcher which
- *	  uses the background worker infrastructure to start the logical
- *	  replication workers for every enabled subscription.
+ *	  This module contains the replication worker launcher which
+ *	  uses the background worker infrastructure to:
+ *	  a) start the logical replication workers for every enabled subscription
+ *	     when not in standby_mode.
+ *	  b) start the slot sync worker for logical failover slots synchronization
+ *	     from the primary server when in standby_mode.
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
+#include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup.h"
 #include "access/htup_details.h"
 #include "access/tableam.h"
 #include "access/xact.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_database.h"
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_subscription_rel.h"
 #include "funcapi.h"
@@ -44,6 +51,7 @@
 #include "storage/procsignal.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/ps_status.h"
@@ -57,6 +65,9 @@
 int			max_logical_replication_workers = 4;
 int			max_sync_workers_per_subscription = 2;
 int			max_parallel_apply_workers_per_subscription = 2;
+bool		enable_syncslot = false;
+
+SlotSyncWorkerInfo *SlotSyncWorker = NULL;
 
 LogicalRepWorker *MyLogicalRepWorker = NULL;
 
@@ -70,6 +81,7 @@ typedef struct LogicalRepCtxStruct
 	dshash_table_handle last_start_dsh;
 
 	/* Background workers. */
+	SlotSyncWorkerInfo ss_worker;	/* slot sync worker */
 	LogicalRepWorker workers[FLEXIBLE_ARRAY_MEMBER];
 } LogicalRepCtxStruct;
 
@@ -102,6 +114,7 @@ static void logicalrep_launcher_onexit(int code, Datum arg);
 static void logicalrep_worker_onexit(int code, Datum arg);
 static void logicalrep_worker_detach(void);
 static void logicalrep_worker_cleanup(LogicalRepWorker *worker);
+static void slotsync_worker_cleanup(SlotSyncWorkerInfo *worker);
 static int	logicalrep_pa_worker_count(Oid subid);
 static void logicalrep_launcher_attach_dshmem(void);
 static void ApplyLauncherSetWorkerStartTime(Oid subid, TimestampTz start_time);
@@ -178,6 +191,8 @@ get_subscription_list(void)
 }
 
 /*
+ * This is common code for logical workers and slot sync worker.
+ *
  * Wait for a background worker to start up and attach to the shmem context.
  *
  * This is only needed for cleaning up the shared memory in case the worker
@@ -186,12 +201,14 @@ get_subscription_list(void)
  * Returns whether the attach was successful.
  */
 static bool
-WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
+WaitForReplicationWorkerAttach(LogicalWorkerHeader *worker,
 							   uint16 generation,
-							   BackgroundWorkerHandle *handle)
+							   BackgroundWorkerHandle *handle,
+							   LWLock *lock)
 {
 	BgwHandleStatus status;
 	int			rc;
+	bool		is_slotsync_worker = (lock == SlotSyncWorkerLock) ? true : false;
 
 	for (;;)
 	{
@@ -199,27 +216,32 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 
 		CHECK_FOR_INTERRUPTS();
 
-		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		LWLockAcquire(lock, LW_SHARED);
 
 		/* Worker either died or has started. Return false if died. */
 		if (!worker->in_use || worker->proc)
 		{
-			LWLockRelease(LogicalRepWorkerLock);
+			LWLockRelease(lock);
 			return worker->in_use;
 		}
 
-		LWLockRelease(LogicalRepWorkerLock);
+		LWLockRelease(lock);
 
 		/* Check if worker has died before attaching, and clean up after it. */
 		status = GetBackgroundWorkerPid(handle, &pid);
 
 		if (status == BGWH_STOPPED)
 		{
-			LWLockAcquire(LogicalRepWorkerLock, LW_EXCLUSIVE);
+			LWLockAcquire(lock, LW_EXCLUSIVE);
 			/* Ensure that this was indeed the worker we waited for. */
 			if (generation == worker->generation)
-				logicalrep_worker_cleanup(worker);
-			LWLockRelease(LogicalRepWorkerLock);
+			{
+				if (is_slotsync_worker)
+					slotsync_worker_cleanup((SlotSyncWorkerInfo *) worker);
+				else
+					logicalrep_worker_cleanup((LogicalRepWorker *) worker);
+			}
+			LWLockRelease(lock);
 			return false;
 		}
 
@@ -262,8 +284,8 @@ logicalrep_worker_find(Oid subid, Oid relid, bool only_running)
 		if (isParallelApplyWorker(w))
 			continue;
 
-		if (w->in_use && w->subid == subid && w->relid == relid &&
-			(!only_running || w->proc))
+		if (w->hdr.in_use && w->subid == subid && w->relid == relid &&
+			(!only_running || w->hdr.proc))
 		{
 			res = w;
 			break;
@@ -290,7 +312,8 @@ logicalrep_workers_find(Oid subid, bool only_running)
 	{
 		LogicalRepWorker *w = &LogicalRepCtx->workers[i];
 
-		if (w->in_use && w->subid == subid && (!only_running || w->proc))
+		if (w->hdr.in_use && w->subid == subid &&
+			(!only_running || w->hdr.proc))
 			res = lappend(res, w);
 	}
 
@@ -351,7 +374,7 @@ retry:
 	{
 		LogicalRepWorker *w = &LogicalRepCtx->workers[i];
 
-		if (!w->in_use)
+		if (!w->hdr.in_use)
 		{
 			worker = w;
 			slot = i;
@@ -380,7 +403,7 @@ retry:
 			 * If the worker was marked in use but didn't manage to attach in
 			 * time, clean it up.
 			 */
-			if (w->in_use && !w->proc &&
+			if (w->hdr.in_use && !w->hdr.proc &&
 				TimestampDifferenceExceeds(w->launch_time, now,
 										   wal_receiver_timeout))
 			{
@@ -438,9 +461,9 @@ retry:
 	/* Prepare the worker slot. */
 	worker->type = wtype;
 	worker->launch_time = now;
-	worker->in_use = true;
-	worker->generation++;
-	worker->proc = NULL;
+	worker->hdr.in_use = true;
+	worker->hdr.generation++;
+	worker->hdr.proc = NULL;
 	worker->dbid = dbid;
 	worker->userid = userid;
 	worker->subid = subid;
@@ -457,7 +480,7 @@ retry:
 	TIMESTAMP_NOBEGIN(worker->reply_time);
 
 	/* Before releasing lock, remember generation for future identification. */
-	generation = worker->generation;
+	generation = worker->hdr.generation;
 
 	LWLockRelease(LogicalRepWorkerLock);
 
@@ -510,7 +533,7 @@ retry:
 	{
 		/* Failed to start worker, so clean up the worker slot. */
 		LWLockAcquire(LogicalRepWorkerLock, LW_EXCLUSIVE);
-		Assert(generation == worker->generation);
+		Assert(generation == worker->hdr.generation);
 		logicalrep_worker_cleanup(worker);
 		LWLockRelease(LogicalRepWorkerLock);
 
@@ -522,19 +545,23 @@ retry:
 	}
 
 	/* Now wait until it attaches. */
-	return WaitForReplicationWorkerAttach(worker, generation, bgw_handle);
+	return WaitForReplicationWorkerAttach((LogicalWorkerHeader *) worker,
+										  generation,
+										  bgw_handle,
+										  LogicalRepWorkerLock);
 }
 
 /*
  * Internal function to stop the worker and wait until it detaches from the
- * slot.
+ * slot. It is used for both logical workers and slot sync worker.
  */
 static void
-logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
+logicalrep_worker_stop_internal(LogicalWorkerHeader *worker, int signo,
+								LWLock *lock)
 {
 	uint16		generation;
 
-	Assert(LWLockHeldByMeInMode(LogicalRepWorkerLock, LW_SHARED));
+	Assert(LWLockHeldByMeInMode(lock, LW_SHARED));
 
 	/*
 	 * Remember which generation was our worker so we can check if what we see
@@ -550,7 +577,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 	{
 		int			rc;
 
-		LWLockRelease(LogicalRepWorkerLock);
+		LWLockRelease(lock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
 		rc = WaitLatch(MyLatch,
@@ -564,7 +591,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		}
 
 		/* Recheck worker status. */
-		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		LWLockAcquire(lock, LW_SHARED);
 
 		/*
 		 * Check whether the worker slot is no longer used, which would mean
@@ -591,7 +618,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		if (!worker->proc || worker->generation != generation)
 			break;
 
-		LWLockRelease(LogicalRepWorkerLock);
+		LWLockRelease(lock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
 		rc = WaitLatch(MyLatch,
@@ -604,7 +631,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 			CHECK_FOR_INTERRUPTS();
 		}
 
-		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		LWLockAcquire(lock, LW_SHARED);
 	}
 }
 
@@ -623,7 +650,9 @@ logicalrep_worker_stop(Oid subid, Oid relid)
 	if (worker)
 	{
 		Assert(!isParallelApplyWorker(worker));
-		logicalrep_worker_stop_internal(worker, SIGTERM);
+		logicalrep_worker_stop_internal((LogicalWorkerHeader *) worker,
+										SIGTERM,
+										LogicalRepWorkerLock);
 	}
 
 	LWLockRelease(LogicalRepWorkerLock);
@@ -669,8 +698,10 @@ logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
 	/*
 	 * Only stop the worker if the generation matches and the worker is alive.
 	 */
-	if (worker->generation == generation && worker->proc)
-		logicalrep_worker_stop_internal(worker, SIGINT);
+	if (worker->hdr.generation == generation && worker->hdr.proc)
+		logicalrep_worker_stop_internal((LogicalWorkerHeader *) worker,
+										SIGINT,
+										LogicalRepWorkerLock);
 
 	LWLockRelease(LogicalRepWorkerLock);
 }
@@ -696,14 +727,14 @@ logicalrep_worker_wakeup(Oid subid, Oid relid)
 /*
  * Wake up (using latch) the specified logical replication worker.
  *
- * Caller must hold lock, else worker->proc could change under us.
+ * Caller must hold lock, else worker->hdr.proc could change under us.
  */
 void
 logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
 {
 	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
 
-	SetLatch(&worker->proc->procLatch);
+	SetLatch(&worker->hdr.proc->procLatch);
 }
 
 /*
@@ -718,7 +749,7 @@ logicalrep_worker_attach(int slot)
 	Assert(slot >= 0 && slot < max_logical_replication_workers);
 	MyLogicalRepWorker = &LogicalRepCtx->workers[slot];
 
-	if (!MyLogicalRepWorker->in_use)
+	if (!MyLogicalRepWorker->hdr.in_use)
 	{
 		LWLockRelease(LogicalRepWorkerLock);
 		ereport(ERROR,
@@ -727,7 +758,7 @@ logicalrep_worker_attach(int slot)
 						slot)));
 	}
 
-	if (MyLogicalRepWorker->proc)
+	if (MyLogicalRepWorker->hdr.proc)
 	{
 		LWLockRelease(LogicalRepWorkerLock);
 		ereport(ERROR,
@@ -736,7 +767,7 @@ logicalrep_worker_attach(int slot)
 						"another worker, cannot attach", slot)));
 	}
 
-	MyLogicalRepWorker->proc = MyProc;
+	MyLogicalRepWorker->hdr.proc = MyProc;
 	before_shmem_exit(logicalrep_worker_onexit, (Datum) 0);
 
 	LWLockRelease(LogicalRepWorkerLock);
@@ -771,7 +802,9 @@ logicalrep_worker_detach(void)
 			LogicalRepWorker *w = (LogicalRepWorker *) lfirst(lc);
 
 			if (isParallelApplyWorker(w))
-				logicalrep_worker_stop_internal(w, SIGTERM);
+				logicalrep_worker_stop_internal((LogicalWorkerHeader *) w,
+												SIGTERM,
+												LogicalRepWorkerLock);
 		}
 
 		LWLockRelease(LogicalRepWorkerLock);
@@ -794,10 +827,10 @@ logicalrep_worker_cleanup(LogicalRepWorker *worker)
 	Assert(LWLockHeldByMeInMode(LogicalRepWorkerLock, LW_EXCLUSIVE));
 
 	worker->type = WORKERTYPE_UNKNOWN;
-	worker->in_use = false;
-	worker->proc = NULL;
-	worker->dbid = InvalidOid;
+	worker->hdr.in_use = false;
+	worker->hdr.proc = NULL;
 	worker->userid = InvalidOid;
+	worker->dbid = InvalidOid;
 	worker->subid = InvalidOid;
 	worker->relid = InvalidOid;
 	worker->leader_pid = InvalidPid;
@@ -931,9 +964,18 @@ ApplyLauncherRegister(void)
 	memset(&bgw, 0, sizeof(bgw));
 	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
 		BGWORKER_BACKEND_DATABASE_CONNECTION;
-	bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
+
+	/*
+	 * The launcher now takes care of launching both logical apply workers and
+	 * logical slot sync worker. Thus to cater to the requirements of both,
+	 * start it as soon as a consistent state is reached. This will help
+	 * slot sync worker to start timely on a physical standby while on a
+	 * non-standby server, it holds same meaning as that of
+	 * BgWorkerStart_RecoveryFinished.
+	 */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState;
 	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
-	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ApplyLauncherMain");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "LauncherMain");
 	snprintf(bgw.bgw_name, BGW_MAXLEN,
 			 "logical replication launcher");
 	snprintf(bgw.bgw_type, BGW_MAXLEN,
@@ -1115,13 +1157,455 @@ ApplyLauncherWakeup(void)
 }
 
 /*
- * Main loop for the apply launcher process.
+ * Clean up slot sync worker info.
+ */
+static void
+slotsync_worker_cleanup(SlotSyncWorkerInfo *worker)
+{
+	Assert(LWLockHeldByMeInMode(SlotSyncWorkerLock, LW_EXCLUSIVE));
+
+	worker->hdr.in_use = false;
+	worker->hdr.proc = NULL;
+	worker->last_update_time = 0;
+}
+
+/*
+ * Attach slot sync worker to SlotSyncWorkerInfo assigned by the launcher.
  */
 void
-ApplyLauncherMain(Datum main_arg)
+slotsync_worker_attach()
 {
-	ereport(DEBUG1,
-			(errmsg_internal("logical replication launcher started")));
+	/* Block concurrent access. */
+	LWLockAcquire(SlotSyncWorkerLock, LW_EXCLUSIVE);
+
+	SlotSyncWorker = &LogicalRepCtx->ss_worker;
+
+	if (!SlotSyncWorker->hdr.in_use)
+	{
+		LWLockRelease(SlotSyncWorkerLock);
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("replication slot sync worker not initialized, cannot attach")));
+	}
+
+	if (SlotSyncWorker->hdr.proc)
+	{
+		LWLockRelease(SlotSyncWorkerLock);
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("replication slot sync worker is already running, cannot attach")));
+	}
+
+	SlotSyncWorker->hdr.proc = MyProc;
+
+	before_shmem_exit(slotsync_worker_detach, (Datum) 0);
+
+	LWLockRelease(SlotSyncWorkerLock);
+}
+
+/*
+ * Detach the slot sync worker (cleans up the worker info).
+ */
+void
+slotsync_worker_detach(int code, Datum arg)
+{
+	LWLockAcquire(SlotSyncWorkerLock, LW_EXCLUSIVE);
+	slotsync_worker_cleanup(SlotSyncWorker);
+	LWLockRelease(SlotSyncWorkerLock);
+}
+
+/*
+ * Start slot sync background worker.
+ *
+ * Returns true on success, false on failure.
+ */
+static bool
+slotsync_worker_launch()
+{
+	BackgroundWorker bgw;
+	BackgroundWorkerHandle *bgw_handle;
+	SlotSyncWorkerInfo *worker;
+	bool		attach;
+	uint16		generation;
+
+	/* The shared memory must only be modified under lock. */
+	LWLockAcquire(SlotSyncWorkerLock, LW_EXCLUSIVE);
+
+	SlotSyncWorker = &LogicalRepCtx->ss_worker;
+
+	worker = SlotSyncWorker;
+
+	/* Prepare the new worker. */
+	worker->hdr.in_use = true;
+
+	/* TODO: do we really need 'generation', analyse more here */
+	worker->hdr.generation++;
+
+	/*
+	 * 'proc' will be assigned in ReplSlotSyncWorkerMain when the worker
+	 * attaches to SlotSyncWorkerInfo.
+	 */
+	worker->hdr.proc = NULL;
+
+	/* Before releasing lock, remember generation for future identification. */
+	generation = worker->hdr.generation;
+
+	LWLockRelease(SlotSyncWorkerLock);
+
+	/* Register the new dynamic worker. */
+	memset(&bgw, 0, sizeof(bgw));
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState;
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+
+	snprintf(bgw.bgw_name, BGW_MAXLEN, "replication slot sync worker");
+
+	snprintf(bgw.bgw_type, BGW_MAXLEN, "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_NEVER_RESTART;
+	bgw.bgw_notify_pid = MyProcPid;
+
+	if (!RegisterDynamicBackgroundWorker(&bgw, &bgw_handle))
+	{
+		/* Failed to start worker, so clean up the worker slot. */
+		LWLockAcquire(SlotSyncWorkerLock, LW_EXCLUSIVE);
+		Assert(generation == worker->hdr.generation);
+		slotsync_worker_cleanup(worker);
+		LWLockRelease(SlotSyncWorkerLock);
+
+		ereport(WARNING,
+				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+				 errmsg("out of background worker slots"),
+				 errhint("You might need to increase %s.",
+						 "max_worker_processes")));
+		return false;
+	}
+
+	/* Now wait until it attaches. */
+	attach = WaitForReplicationWorkerAttach((LogicalWorkerHeader *) worker,
+											generation,
+											bgw_handle,
+											SlotSyncWorkerLock);
+
+	if (!attach)
+		ereport(WARNING,
+				(errmsg("replication slot sync worker failed to attach")));
+
+	return attach;
+}
+
+/*
+ * Internal function to stop the slot sync worker and cleanup afterwards.
+ */
+static void
+slotsync_worker_stop_internal(SlotSyncWorkerInfo *worker)
+{
+	LWLockAcquire(SlotSyncWorkerLock, LW_SHARED);
+	ereport(LOG,
+			(errmsg("stopping replication slot sync worker with pid: %d",
+					worker->hdr.proc->pid)));
+	logicalrep_worker_stop_internal((LogicalWorkerHeader *) worker,
+									SIGINT,
+									SlotSyncWorkerLock);
+	LWLockRelease(SlotSyncWorkerLock);
+}
+
+/*
+ * Validate if db with given dbname exists.
+ *
+ * Can't use existing functions like 'get_database_oid' from dbcommands.c for
+ * validity purpose as they need db connection.
+ */
+static bool
+validate_dbname(const char *dbname)
+{
+	HeapTuple	tuple;
+	Relation	relation;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	bool		valid;
+
+	/* Start a transaction so we can access pg_database */
+	StartTransactionCommand();
+
+	/* Form a scan key */
+	ScanKeyInit(&key[0], Anum_pg_database_datname, BTEqualStrategyNumber,
+				F_NAMEEQ, CStringGetDatum(dbname));
+
+	/* No db connection, force heap scan */
+	relation = table_open(DatabaseRelationId, AccessShareLock);
+	scan = systable_beginscan(relation, DatabaseNameIndexId, false,
+							  NULL, 1, key);
+
+	tuple = systable_getnext(scan);
+
+	if (HeapTupleIsValid(tuple))
+		valid = true;
+	else
+		valid = false;
+
+	/* all done */
+	systable_endscan(scan);
+	table_close(relation, AccessShareLock);
+
+	CommitTransactionCommand();
+	return valid;
+}
+
+/*
+ * Checks if GUC are set appropriately before starting slot sync worker
+ */
+static bool
+slotsync_checks(long *wait_time, bool *retry)
+{
+	char	   *dbname;
+
+	*retry = false;
+
+	if (!enable_syncslot)
+		return false;
+
+	/*
+	 * Since the above GUC is set, check that other GUC settings
+	 * (primary_slot_name, hot_standby_feedback, primary_conninfo, wal_level)
+	 * are compatible with slot synchronization. If not, issue warnings.
+	 */
+
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (!WalRcv || WalRcv->slotname[0] == '\0')
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronization as primary_slot_name is not set"));
+
+		/*
+		 * It's possible that the Walreceiver has not been started yet, adjust
+		 * the wait_time to retry sooner in the next synchronization cycle.
+		 */
+		*wait_time = wal_retrieve_retry_interval;
+
+		/*
+		 * Tell caller to retry the connection for the case where
+		 * primary_slot_name is set but Walreceiver is not yet started.
+		 */
+		if (PrimarySlotName && strcmp(PrimarySlotName, "") != 0)
+			*retry = true;
+
+		return false;
+	}
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronization as hot_standby_feedback is off"));
+		return false;
+	}
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronisation as it requires wal_level >= logical"));
+		return false;
+	}
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (dbname == NULL)
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronization as dbname is not specified in primary_conninfo"));
+		return false;
+	}
+
+	if (!validate_dbname(dbname))
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronization as dbname specified in primary_conninfo is not a valid one"));
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	if (LogicalRepCtx->ss_worker.hdr.in_use)
+		slotsync_worker_stop_internal(&LogicalRepCtx->ss_worker);
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If one of the slot sync options has changed, stop the slot sync worker
+ * and set ss_recheck flag to enable the caller to recheck slot sync GUCs
+ * before restarting the worker
+ */
+static void
+LauncherRereadConfig(bool *ss_recheck)
+{
+	char	   *conninfo = pstrdup(PrimaryConnInfo);
+	char	   *slotname = pstrdup(PrimarySlotName);
+	bool		syncslot = enable_syncslot;
+	bool		standbyfeedback = hot_standby_feedback;
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	/*
+	 * If any of the related GUCs changed, stop the slot sync worker. The
+	 * worker will be relaunched in next sync-cycle using the new GUCs.
+	 */
+	if ((strcmp(conninfo, PrimaryConnInfo) != 0) ||
+		(strcmp(slotname, PrimarySlotName) != 0) ||
+		(syncslot != enable_syncslot) ||
+		(standbyfeedback != hot_standby_feedback))
+	{
+		ShutDownSlotSync();
+
+		/* Retry slot sync with new GUCs */
+		*ss_recheck = true;
+	}
+
+	pfree(conninfo);
+	pfree(slotname);
+}
+
+/*
+ * Launch slot sync background worker.
+ */
+static void
+LaunchSlotSyncWorker(long *wait_time)
+{
+	LWLockAcquire(SlotSyncWorkerLock, LW_SHARED);
+
+	/* The worker is running already */
+	if (SlotSyncWorker && SlotSyncWorker->hdr.in_use &&
+		SlotSyncWorker->hdr.proc)
+	{
+		LWLockRelease(SlotSyncWorkerLock);
+		return;
+	}
+
+	LWLockRelease(SlotSyncWorkerLock);
+
+	/*
+	 * If launch failed, adjust the wait_time to retry in the next sync-cycle
+	 * sooner.
+	 */
+	if (!slotsync_worker_launch())
+	{
+		*wait_time = Min(*wait_time, wal_retrieve_retry_interval);
+	}
+}
+
+/*
+ * Launch logical replication apply workers for enabled subscriptions.
+ */
+static void
+LaunchSubscriptionApplyWorker(long *wait_time)
+{
+	List	   *sublist;
+	ListCell   *lc;
+	MemoryContext subctx;
+	MemoryContext oldctx;
+
+	/* Use temporary context to avoid leaking memory across cycles. */
+	subctx = AllocSetContextCreate(TopMemoryContext,
+								   "Logical Replication Launcher sublist",
+								   ALLOCSET_DEFAULT_SIZES);
+	oldctx = MemoryContextSwitchTo(subctx);
+
+	/* Start any missing workers for enabled subscriptions. */
+	sublist = get_subscription_list();
+	foreach(lc, sublist)
+	{
+		Subscription *sub = (Subscription *) lfirst(lc);
+		LogicalRepWorker *w;
+		TimestampTz last_start;
+		TimestampTz now;
+		long		elapsed;
+
+		if (!sub->enabled)
+			continue;
+
+		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		w = logicalrep_worker_find(sub->oid, InvalidOid, false);
+		LWLockRelease(LogicalRepWorkerLock);
+
+		if (w != NULL)
+			continue;			/* worker is running already */
+
+		/*
+		 * If the worker is eligible to start now, launch it.  Otherwise,
+		 * adjust wait_time so that we'll wake up as soon as it can be
+		 * started.
+		 *
+		 * Each subscription's apply worker can only be restarted once per
+		 * wal_retrieve_retry_interval, so that errors do not cause us to
+		 * repeatedly restart the worker as fast as possible.  In cases where
+		 * a restart is expected (e.g., subscription parameter changes),
+		 * another process should remove the last-start entry for the
+		 * subscription so that the worker can be restarted without waiting
+		 * for wal_retrieve_retry_interval to elapse.
+		 */
+		last_start = ApplyLauncherGetWorkerStartTime(sub->oid);
+		now = GetCurrentTimestamp();
+		if (last_start == 0 ||
+			(elapsed = TimestampDifferenceMilliseconds(last_start, now)) >=
+			wal_retrieve_retry_interval)
+		{
+			ApplyLauncherSetWorkerStartTime(sub->oid, now);
+			logicalrep_worker_launch(WORKERTYPE_APPLY,
+									 sub->dbid, sub->oid, sub->name,
+									 sub->owner, InvalidOid,
+									 DSM_HANDLE_INVALID);
+		}
+		else
+		{
+			*wait_time = Min(*wait_time,
+							 wal_retrieve_retry_interval - elapsed);
+		}
+	}
+
+	/* Switch back to original memory context. */
+	MemoryContextSwitchTo(oldctx);
+	/* Clean the temporary memory. */
+	MemoryContextDelete(subctx);
+}
+
+/*
+ * Main loop for the launcher process.
+ */
+void
+LauncherMain(Datum main_arg)
+{
+	bool		start_slotsync = false;
+	bool		recheck_slotsync = true;
+
+	elog(DEBUG1, "logical replication launcher started");
 
 	before_shmem_exit(logicalrep_launcher_onexit, (Datum) 0);
 
@@ -1139,79 +1623,32 @@ ApplyLauncherMain(Datum main_arg)
 	 */
 	BackgroundWorkerInitializeConnection(NULL, NULL, 0);
 
+	load_file("libpqwalreceiver", false);
+
 	/* Enter main loop */
 	for (;;)
 	{
 		int			rc;
-		List	   *sublist;
-		ListCell   *lc;
-		MemoryContext subctx;
-		MemoryContext oldctx;
 		long		wait_time = DEFAULT_NAPTIME_PER_CYCLE;
 
 		CHECK_FOR_INTERRUPTS();
 
-		/* Use temporary context to avoid leaking memory across cycles. */
-		subctx = AllocSetContextCreate(TopMemoryContext,
-									   "Logical Replication Launcher sublist",
-									   ALLOCSET_DEFAULT_SIZES);
-		oldctx = MemoryContextSwitchTo(subctx);
-
-		/* Start any missing workers for enabled subscriptions. */
-		sublist = get_subscription_list();
-		foreach(lc, sublist)
+		/*
+		 * If it is Hot standby, then try to launch slot sync worker else
+		 * launch apply workers.
+		 */
+		if (RecoveryInProgress() && !PromoteIsTriggered())
 		{
-			Subscription *sub = (Subscription *) lfirst(lc);
-			LogicalRepWorker *w;
-			TimestampTz last_start;
-			TimestampTz now;
-			long		elapsed;
-
-			if (!sub->enabled)
-				continue;
-
-			LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
-			w = logicalrep_worker_find(sub->oid, InvalidOid, false);
-			LWLockRelease(LogicalRepWorkerLock);
+			/* Make validation checks first */
+			if (recheck_slotsync)
+				start_slotsync = slotsync_checks(&wait_time, &recheck_slotsync);
 
-			if (w != NULL)
-				continue;		/* worker is running already */
-
-			/*
-			 * If the worker is eligible to start now, launch it.  Otherwise,
-			 * adjust wait_time so that we'll wake up as soon as it can be
-			 * started.
-			 *
-			 * Each subscription's apply worker can only be restarted once per
-			 * wal_retrieve_retry_interval, so that errors do not cause us to
-			 * repeatedly restart the worker as fast as possible.  In cases
-			 * where a restart is expected (e.g., subscription parameter
-			 * changes), another process should remove the last-start entry
-			 * for the subscription so that the worker can be restarted
-			 * without waiting for wal_retrieve_retry_interval to elapse.
-			 */
-			last_start = ApplyLauncherGetWorkerStartTime(sub->oid);
-			now = GetCurrentTimestamp();
-			if (last_start == 0 ||
-				(elapsed = TimestampDifferenceMilliseconds(last_start, now)) >= wal_retrieve_retry_interval)
-			{
-				ApplyLauncherSetWorkerStartTime(sub->oid, now);
-				logicalrep_worker_launch(WORKERTYPE_APPLY,
-										 sub->dbid, sub->oid, sub->name,
-										 sub->owner, InvalidOid,
-										 DSM_HANDLE_INVALID);
-			}
-			else
-			{
-				wait_time = Min(wait_time,
-								wal_retrieve_retry_interval - elapsed);
-			}
+			/* Start slot sync workers if checks passed */
+			if (start_slotsync)
+				LaunchSlotSyncWorker(&wait_time);
 		}
-
-		/* Switch back to original memory context. */
-		MemoryContextSwitchTo(oldctx);
-		/* Clean the temporary memory. */
-		MemoryContextDelete(subctx);
+		else
+			LaunchSubscriptionApplyWorker(&wait_time);
 
 		/* Wait for more work. */
 		rc = WaitLatch(MyLatch,
@@ -1226,10 +1663,7 @@ ApplyLauncherMain(Datum main_arg)
 		}
 
 		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
-		}
+			LauncherRereadConfig(&recheck_slotsync);
 	}
 
 	/* Not reachable */
@@ -1260,7 +1694,8 @@ GetLeaderApplyWorkerPid(pid_t pid)
 	{
 		LogicalRepWorker *w = &LogicalRepCtx->workers[i];
 
-		if (isParallelApplyWorker(w) && w->proc && pid == w->proc->pid)
+		if (isParallelApplyWorker(w) && w->hdr.proc &&
+			pid == w->hdr.proc->pid)
 		{
 			leader_pid = w->leader_pid;
 			break;
@@ -1298,13 +1733,13 @@ pg_stat_get_subscription(PG_FUNCTION_ARGS)
 
 		memcpy(&worker, &LogicalRepCtx->workers[i],
 			   sizeof(LogicalRepWorker));
-		if (!worker.proc || !IsBackendPid(worker.proc->pid))
+		if (!worker.hdr.proc || !IsBackendPid(worker.hdr.proc->pid))
 			continue;
 
 		if (OidIsValid(subid) && worker.subid != subid)
 			continue;
 
-		worker_pid = worker.proc->pid;
+		worker_pid = worker.hdr.proc->pid;
 
 		values[0] = ObjectIdGetDatum(worker.subid);
 		if (isTablesyncWorker(&worker))
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..5bef0138b4 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,28 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	/*
+	 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+	 * promotion.
+	 */
+	if (!RecoveryInProgress() && slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+		elog(ERROR, "replication slot \"%s\" was not synced completely from the primary server",
+			 NameStr(slot->data.name));
+
+	/*
+	 * Do not allow consumption of a "synchronized" slot until the standby
+	 * gets promoted. Also do not allow consumption of slots with sync_state
+	 * as SYNCSLOT_STATE_INITIATED as they are not synced completely to be
+	 * used.
+	 */
+	if (RecoveryInProgress() && slot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot use replication slot \"%s\" for logical decoding",
+						NameStr(slot->data.name)),
+				 errdetail("This slot is being synced from the primary server."),
+				 errhint("Specify another replication slot.")));
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..61bbf42933
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1032 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * It also takes care of dropping the slots which were created by it and are
+ * currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		conflicting;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+/*
+ * Number of attempts for wait_for_primary_slot_catchup() after
+ * which it aborts the wait and the slot sync worker then moves
+ * to the next slot creation/sync.
+ */
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * *persist will be set to false if the slot has disappeared or was invalidated
+ * on the primary; otherwise, it will be set to true.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *persist)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+	*persist = true;
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn, confirmed_flush_lsn,"
+					 " catalog_xmin FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		XLogRecPtr	new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *slot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		Assert(RecoveryInProgress());
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot info for slot \"%s\" from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" disappeared from the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			*persist = false;
+
+			return false;
+		}
+
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(slot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" invalidated on the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			ExecClearTuple(slot);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * Once we got valid restart_lsn, then confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(slot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\" and moving"
+						   " to the next slot, will attempt creating it again",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Wait for remote slot to pass locally reserved position and
+ * sync the slot locally if wait is over.
+ */
+static void
+wait_for_primary_and_sync(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+						  bool *slot_updated)
+{
+	/*
+	 * If the local restart_lsn and/or local catalog_xmin is ahead of those on
+	 * the remote then we cannot create the local slot in sync with the
+	 * primary server because that would mean moving the local slot backwards
+	 * and we might not have WALs retained for old LSN. In this case we will
+	 * wait for the primary server's restart_lsn and catalog_xmin to catch up
+	 * with the local one before attempting the sync.
+	 */
+	if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+		TransactionIdPrecedes(remote_slot->catalog_xmin,
+							  MyReplicationSlot->data.catalog_xmin))
+	{
+		bool		persist;
+
+		if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &persist))
+		{
+			/*
+			 * The remote slot didn't catch up to locally reserved position.
+			 *
+			 * We do not drop the slot because the restart_lsn can be ahead of
+			 * the current location when recreating the slot in the next cycle.
+			 * It may take more time to create such a slot. Therefore, we
+			 * persist it and attempt the wait and synchronization in the next
+			 * cycle.
+			 */
+			if (persist && MyReplicationSlot->data.persistency != RS_PERSISTENT)
+			{
+				ReplicationSlotPersist();
+				*slot_updated = true;
+			}
+
+			return;
+		}
+	}
+
+	/* Update LSN of slot to remote slot's current position */
+	local_slot_update(remote_slot);
+
+	if (MyReplicationSlot->data.persistency != RS_PERSISTENT)
+		ReplicationSlotPersist();
+	else
+		ReplicationSlotSave();
+
+	/*
+	 * Wait for primary is over, mark the slot as READY for incremental syncs.
+	 * Cascading standbys can also start syncing it.
+	 */
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	*slot_updated = true;
+
+	ereport(LOG, errmsg("newly locally created slot \"%s\" has been synced",
+						remote_slot->name));
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up.
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			slots = lappend(slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(slots);
+}
+
+/*
+ * Get list of local logical slot names which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slot_names(void)
+{
+	List	   *localSyncedSlots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			localSyncedSlots = lappend(localSyncedSlots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return localSyncedSlots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+slot_exists_in_list(ReplicationSlot *local_slot, List *remote_slots,
+					bool *locally_invalidated)
+{
+	ListCell   *cell;
+
+	foreach(cell, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * if remote slot is marked as non-conflicting (i.e. not
+			 * invalidated) but local slot is marked as invalidated, then set
+			 * the bool.
+			 */
+			*locally_invalidated =
+				!remote_slot->conflicting &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * This gets invalidation cause of the remote slot.
+ */
+static ReplicationSlotInvalidationCause
+get_remote_invalidation_cause(WalReceiverConn *wrconn, char *slot_name)
+{
+	WalRcvExecResult *res;
+	Oid			slotRow[1] = {INT2OID};
+	StringInfoData cmd;
+	bool		isnull;
+	TupleTableSlot *slot;
+	ReplicationSlotInvalidationCause cause;
+	MemoryContext oldctx = CurrentMemoryContext;
+
+	/* Syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	/* Make things live outside TX context */
+	MemoryContextSwitchTo(oldctx);
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT pg_get_slot_invalidation_cause(%s)",
+					 quote_literal_cstr(slot_name));
+	res = walrcv_exec(wrconn, cmd.data, 1, slotRow);
+	pfree(cmd.data);
+
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch invalidation cause for slot \"%s\" from"
+						" the primary server: %s", slot_name, res->err)));
+
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		ereport(ERROR,
+				(errmsg("slot \"%s\" disappeared from the primary server",
+						slot_name)));
+
+	cause = DatumGetInt16(slot_getattr(slot, 1, &isnull));
+	Assert(!isnull);
+
+	ExecClearTuple(slot);
+	walrcv_clear_result(res);
+
+	return cause;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Also drop the slots that are valid on the primary that got invalidated
+ * on the standby due to conflict (say required rows removed on the primary).
+ * The assumption is, that these will get recreated in next sync-cycle and
+ * it is okay to drop and recreate such slots as long as these are not
+ * consumable on the standby (which is the case currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slot_list = NIL;
+	ListCell   *lc_slot;
+
+	/*
+	 * Get the list of local 'synced' slot so that those not on remote could
+	 * be dropped.
+	 */
+	local_slot_list = get_local_synced_slot_names();
+
+	foreach(lc_slot, local_slot_list)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc_slot);
+		bool		local_exists = false;
+		bool		locally_invalidated = false;
+
+		local_exists = slot_exists_in_list(local_slot, remote_slot_list,
+										   &locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!local_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Constructs the query in order to get failover logical slots
+ * information from the primary server.
+ */
+static void
+construct_slot_query(StringInfo s)
+{
+	/*
+	 * Fetch data for logical failover slots with sync_state either as
+	 * SYNCSLOT_STATE_NONE or SYNCSLOT_STATE_READY.
+	 */
+	appendStringInfo(s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, conflicting, "
+					 " database FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover and sync_state != 'i'");
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ */
+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+					 bool *slot_updated)
+{
+	MemoryContext oldctx = CurrentMemoryContext;
+	ReplicationSlot *s;
+	char		sync_state = 0;
+
+	/*
+	 * Make sure that concerned WAL is received before syncing slot to target
+	 * lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot. But to
+	 * take care of any bug in that flow, we should retain this check.
+	 */
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		elog(LOG, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+
+		return;
+	}
+
+	/* Search for the named slot */
+	if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		SpinLockAcquire(&s->mutex);
+		sync_state = s->data.sync_state;
+		SpinLockRelease(&s->mutex);
+	}
+
+	StartTransactionCommand();
+
+	/* Make things live outside TX context */
+	MemoryContextSwitchTo(oldctx);
+
+	/*
+	 * Already existing slot (created by slot sync worker) and ready for sync,
+	 * acquire and sync it.
+	 */
+	if (sync_state == SYNCSLOT_STATE_READY)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&MyReplicationSlot->mutex);
+			MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&MyReplicationSlot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn)
+		{
+			ereport(WARNING,
+					errmsg("not synchronizing slot %s; synchronization would"
+						   " move it backwards", remote_slot->name));
+
+			goto cleanup;
+		}
+
+		if (remote_slot->confirmed_lsn != MyReplicationSlot->data.confirmed_flush ||
+			remote_slot->restart_lsn != MyReplicationSlot->data.restart_lsn ||
+			remote_slot->catalog_xmin != MyReplicationSlot->data.catalog_xmin)
+		{
+			/* Update LSN of slot to remote slot's current position */
+			local_slot_update(remote_slot);
+			ReplicationSlotSave();
+			*slot_updated = true;
+		}
+	}
+
+	/*
+	 * Already existing slot but not ready (i.e. waiting for the primary
+	 * server to catch-up), lets attempt to finish the first sync now.
+	 */
+	else if (sync_state == SYNCSLOT_STATE_INITIATED)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		wait_for_primary_and_sync(wrconn, remote_slot, slot_updated);
+	}
+	/* User created slot with the same name exists, raise ERROR. */
+	else if (sync_state == SYNCSLOT_STATE_NONE)
+	{
+		ereport(ERROR,
+				errmsg("not synchronizing slot %s; it is a user created slot",
+					   remote_slot->name));
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+		ReplicationSlot *slot;
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase, false);
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+
+		/* Mark it as sync initiated by slot sync worker */
+		slot->data.sync_state = SYNCSLOT_STATE_INITIATED;
+		slot->data.failover = true;
+
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		wait_for_primary_and_sync(wrconn, remote_slot, slot_updated);
+
+	}
+
+cleanup:
+
+	ReplicationSlotRelease();
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	return;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and update
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns nap time for the next sync-cycle.
+ */
+static long
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 8
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *slot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	MemoryContext oldctx = CurrentMemoryContext;
+	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	ListCell   *cell;
+	bool		slot_updated = false;
+	TimestampTz now;
+
+	/* The primary_slot_name is not set yet or WALs not received yet */
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+		return naptime;
+
+	/* The syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	/* Make things live outside TX context */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct query to get slots info from the primary server */
+	initStringInfo(&s);
+	construct_slot_query(&s);
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(slot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+																	   5, &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(slot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->conflicting = DatumGetBool(slot_getattr(slot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(slot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		if (remote_slot->conflicting)
+			remote_slot->invalidated = get_remote_invalidation_cause(wrconn,
+																	 remote_slot->name);
+		else
+			remote_slot->invalidated = RS_INVAL_NONE;
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(slot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(cell, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		synchronize_one_slot(wrconn, remote_slot, &slot_updated);
+	}
+
+	now = GetCurrentTimestamp();
+
+	/*
+	 * If any of the slots get updated in this sync-cycle, retain default
+	 * naptime and update 'last_update_time' in slot sync worker. But if no
+	 * activity is observed in this sync-cycle, then increase naptime provided
+	 * inactivity time reaches threshold.
+	 */
+	if (slot_updated)
+		SlotSyncWorker->last_update_time = now;
+
+	else if (TimestampDifferenceExceeds(SlotSyncWorker->last_update_time,
+										now, WORKER_INACTIVITY_THRESHOLD_MS))
+		naptime = WORKER_INACTIVITY_NAPTIME_MS;
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	return naptime;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false, "slot sync", &err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If primary_conninfo has changed, reconnect to primary.
+ */
+static void
+slotsync_reread_config(WalReceiverConn **wrconn)
+{
+	char	   *conninfo = pstrdup(PrimaryConnInfo);
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	/* Reconnect if GUC primary_conninfo got changed */
+	if (strcmp(conninfo, PrimaryConnInfo) != 0)
+	{
+		if (*wrconn)
+			walrcv_disconnect(*wrconn);
+
+		*wrconn = remote_connect();
+	}
+
+	pfree(conninfo);
+}
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting"
+					   " down on receiving SIGINT"));
+
+		walrcv_disconnect(*wrconn);
+		proc_exit(0);
+	}
+
+
+	if (ConfigReloadPending)
+		slotsync_reread_config(wrconn);
+}
+
+/*
+ * The main loop of our worker process.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	slotsync_worker_attach();
+
+	/*
+	 * If the standby has been promoted, skip the slot synchronization process.
+	 *
+	 * Although the startup process stops all the slot sync workers on
+	 * promotion, the launcher may not have realized the promotion and could
+	 * start additional workers after that. Therefore, this check is still
+	 * necessary to prevent these additional workers from running.
+	 */
+	if (PromoteIsTriggered())
+		exit(0);
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	/*
+	 * Get the user provided dbname from the connection string, if dbname not
+	 * provided, skip sync.
+	 */
+	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (dbname == NULL)
+		proc_exit(0);
+
+	/*
+	 * Connect to the database specified by user in PrimaryConnInfo. We need a
+	 * database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname,
+										 NULL,
+										 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime;
+
+		ProcessSlotSyncInterrupts(&wrconn);
+
+		/* Check if got promoted */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Drop the slots for which sync is initiated but not yet
+			 * completed i.e. they are still waiting for the primary server to
+			 * catch up.
+			 */
+			slotsync_drop_initiated_slots();
+			ereport(LOG,
+					errmsg("exiting slot sync woker on promotion of standby"));
+			proc_exit(0);
+		}
+
+		naptime = synchronize_slots(wrconn);
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 1eaee4197b..73aacef25f 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "commands/subscriptioncmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
@@ -246,7 +247,7 @@ wait_for_worker_state_change(char expected_state)
 		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
 		worker = logicalrep_worker_find(MyLogicalRepWorker->subid,
 										InvalidOid, false);
-		if (worker && worker->proc)
+		if (worker && worker->hdr.proc)
 			logicalrep_worker_wakeup_ptr(worker);
 		LWLockRelease(LogicalRepWorkerLock);
 		if (!worker)
@@ -535,7 +536,7 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 				if (rstate->state == SUBREL_STATE_SYNCWAIT)
 				{
 					/* Signal the sync worker, as it may be waiting for us. */
-					if (syncworker->proc)
+					if (syncworker->hdr.proc)
 						logicalrep_worker_wakeup_ptr(syncworker);
 
 					/* Now safe to release the LWLock */
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index c96a1700cd..2c059dffca 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -325,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = SYNCSLOT_STATE_NONE;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -684,12 +686,26 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+	{
+		ReplicationSlotRelease();
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary.")));
+	}
+
 	ReplicationSlotDropAcquired();
 }
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index fb6e37d2c3..7b1e0c1552 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3e44228bde..edb3656cd2 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, false);
 }
 
 /*
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 315a78cda9..fd9e73a49b 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -190,6 +190,8 @@ static const char *const BuiltinTrancheNames[] = {
 	"LogicalRepLauncherDSA",
 	/* LWTRANCHE_LAUNCHER_HASH: */
 	"LogicalRepLauncherHash",
+	/* LWTRANCHE_SLOTSYNC_DSA: */
+	"SlotSyncWorkerDSA",
 };
 
 StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index f72f2906ce..e62a3f1bc0 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -54,3 +54,4 @@ XactTruncationLock					44
 WrapLimitsVacuumLock				46
 NotifyQueueTailLock					47
 WaitEventExtensionLock				48
+SlotSyncWorkerLock					49
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index e5e7bb23f9..65a1e544c1 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -65,8 +65,11 @@
 #include "postmaster/syslogger.h"
 #include "postmaster/walwriter.h"
 #include "replication/logicallauncher.h"
+#include "replication/reorderbuffer.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/walreceiver.h"
+#include "replication/walsender.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2021,6 +2024,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_SIGHUP, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 998080e0e4..eaafdb3035 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -355,6 +355,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = on			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d906734750..6a8192ad83 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 214dc6c29e..75b4b2040d 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -17,6 +17,7 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"
 
 extern ObjectAddress CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										bool isTopLevel);
@@ -28,4 +29,7 @@ extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
 extern char defGetStreamingMode(DefElem *def);
 
+extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn,
+										char *slotname, bool missing_ok);
+
 #endif							/* SUBSCRIPTIONCMDS_H */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index bef8a7162e..8a5b374cea 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -33,6 +33,15 @@ typedef struct IdentifySystemCmd
 	NodeTag		type;
 } IdentifySystemCmd;
 
+/* -------------------------------
+ *		LIST_DBID_FOR_FAILOVER_SLOTS command
+ * -------------------------------
+ */
+typedef struct ListDBForFailoverSlotsCmd
+{
+	NodeTag		type;
+	List	   *slot_names;
+}			ListDBForFailoverSlotsCmd;
 
 /* ----------------------
  *		BASE_BACKUP command
diff --git a/src/include/replication/logicallauncher.h b/src/include/replication/logicallauncher.h
index a07c9cb311..02499a7e66 100644
--- a/src/include/replication/logicallauncher.h
+++ b/src/include/replication/logicallauncher.h
@@ -15,9 +15,11 @@
 extern PGDLLIMPORT int max_logical_replication_workers;
 extern PGDLLIMPORT int max_sync_workers_per_subscription;
 extern PGDLLIMPORT int max_parallel_apply_workers_per_subscription;
+extern PGDLLIMPORT bool enable_syncslot;
+
 
 extern void ApplyLauncherRegister(void);
-extern void ApplyLauncherMain(Datum main_arg);
+extern void LauncherMain(Datum main_arg);
 
 extern Size ApplyLauncherShmemSize(void);
 extern void ApplyLauncherShmemInit(void);
@@ -31,4 +33,9 @@ extern bool IsLogicalLauncher(void);
 
 extern pid_t GetLeaderApplyWorkerPid(pid_t pid);
 
+extern void ShutDownSlotSync(void);
+
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+
 #endif							/* LOGICALLAUNCHER_H */
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index bbd71d0b42..baad5a8f3a 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -19,6 +19,7 @@ extern PGDLLIMPORT volatile sig_atomic_t ParallelApplyMessagePending;
 extern void ApplyWorkerMain(Datum main_arg);
 extern void ParallelApplyWorkerMain(Datum main_arg);
 extern void TablesyncWorkerMain(Datum main_arg);
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
 
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index ca06e5b1ad..6c300da45a 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -15,7 +15,6 @@
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "storage/spin.h"
-#include "replication/walreceiver.h"
 
 /*
  * Behaviour of replication slots, upon release or crash.
@@ -52,6 +51,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +119,13 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Is this a slot created by a sync-slot worker?
+	 *
+	 * Relevant for logical slots on the physical standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)?
 	 * Only relevant for logical slots on the primary server.
@@ -227,7 +241,7 @@ extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
 								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
@@ -253,7 +267,6 @@ extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_l
 extern int	ReplicationSlotIndex(ReplicationSlot *slot);
 extern bool ReplicationSlotName(int index, Name name);
 extern void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot);
-extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok);
 
 extern void StartupReplicationSlots(void);
 extern void CheckPointReplicationSlots(bool is_shutdown);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 61bc8de72c..87e45c990e 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -20,6 +20,7 @@
 #include "pgtime.h"
 #include "port/atomics.h"
 #include "replication/logicalproto.h"
+#include "replication/slot.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/latch.h"
@@ -191,6 +192,14 @@ typedef struct
 	}			proto;
 } WalRcvStreamOptions;
 
+/*
+ * Failover logical slots data received from remote.
+ */
+typedef struct WalRcvFailoverSlotsData
+{
+	Oid			dboid;
+}			WalRcvFailoverSlotsData;
+
 struct WalReceiverConn;
 typedef struct WalReceiverConn WalReceiverConn;
 
@@ -280,6 +289,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -404,6 +428,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -429,6 +454,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 4378690ab0..4161de1019 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -1,7 +1,8 @@
 /*-------------------------------------------------------------------------
  *
  * worker_internal.h
- *	  Internal headers shared by logical replication workers.
+ *	  Internal headers shared by logical replication workers
+ *	  and slotsync workers.
  *
  * Portions Copyright (c) 2016-2023, PostgreSQL Global Development Group
  *
@@ -36,14 +37,9 @@ typedef enum LogicalRepWorkerType
 	WORKERTYPE_PARALLEL_APPLY,
 } LogicalRepWorkerType;
 
-typedef struct LogicalRepWorker
+/* Common data for Slotsync and LogicalRep workers */
+typedef struct LogicalWorkerHeader
 {
-	/* What type of worker is this? */
-	LogicalRepWorkerType type;
-
-	/* Time at which this worker was launched. */
-	TimestampTz launch_time;
-
 	/* Indicates if this slot is used or free. */
 	bool		in_use;
 
@@ -53,6 +49,19 @@ typedef struct LogicalRepWorker
 	/* Pointer to proc array. NULL if not running. */
 	PGPROC	   *proc;
 
+} LogicalWorkerHeader;
+
+/* Shared memory structure for logical replication workers. */
+typedef struct LogicalRepWorker
+{
+	LogicalWorkerHeader hdr;
+
+	/* Time at which this worker was launched. */
+	TimestampTz launch_time;
+
+	/* What type of worker is this? */
+	LogicalRepWorkerType type;
+
 	/* Database id to connect to. */
 	Oid			dbid;
 
@@ -96,6 +105,24 @@ typedef struct LogicalRepWorker
 	TimestampTz reply_time;
 } LogicalRepWorker;
 
+/*
+ * Shared memory structure for Slot-Sync worker. It is allocated by logical
+ * replication launcher and then read by each slot sync worker.
+ *
+ * It is protected by LWLock (SlotSyncWorkerLock). Each slot sync worker
+ * reading the structure needs to hold the lock in shared mode, whereas
+ * the logical replication launcher which updates it needs to hold the lock
+ * in exclusive mode.
+ */
+typedef struct SlotSyncWorkerInfo
+{
+	LogicalWorkerHeader hdr;
+
+	/* The last sync-cycle time when the worker updated any of the slots. */
+	TimestampTz last_update_time;
+
+}			SlotSyncWorkerInfo;
+
 /*
  * State of the transaction in parallel apply worker.
  *
@@ -234,12 +261,16 @@ extern PGDLLIMPORT struct WalReceiverConn *LogRepWorkerWalRcvConn;
 /* Worker and subscription objects. */
 extern PGDLLIMPORT Subscription *MySubscription;
 extern PGDLLIMPORT LogicalRepWorker *MyLogicalRepWorker;
+extern PGDLLIMPORT SlotSyncWorkerInfo *SlotSyncWorker;
 
 extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
 extern void logicalrep_worker_attach(int slot);
+extern void slotsync_worker_attach(void);
+extern void slotsync_worker_detach(int code, Datum arg);
+extern void slotsync_drop_initiated_slots(void);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
 extern List *logicalrep_workers_find(Oid subid, bool only_running);
@@ -328,9 +359,9 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
-#define isParallelApplyWorker(worker) ((worker)->in_use && \
+#define isParallelApplyWorker(worker) ((worker)->hdr.in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
-#define isTablesyncWorker(worker) ((worker)->in_use && \
+#define isTablesyncWorker(worker) ((worker)->hdr.in_use && \
 								   (worker)->type == WORKERTYPE_TABLESYNC)
 
 static inline bool
@@ -342,14 +373,14 @@ am_tablesync_worker(void)
 static inline bool
 am_leader_apply_worker(void)
 {
-	Assert(MyLogicalRepWorker->in_use);
+	Assert(MyLogicalRepWorker->hdr.in_use);
 	return (MyLogicalRepWorker->type == WORKERTYPE_APPLY);
 }
 
 static inline bool
 am_parallel_apply_worker(void)
 {
-	Assert(MyLogicalRepWorker->in_use);
+	Assert(MyLogicalRepWorker->hdr.in_use);
 	return isParallelApplyWorker(MyLogicalRepWorker);
 }
 
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index b038e599c0..0621ee70fc 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -207,6 +207,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_PGSTATS_DATA,
 	LWTRANCHE_LAUNCHER_DSA,
 	LWTRANCHE_LAUNCHER_HASH,
+	LWTRANCHE_SLOTSYNC_DSA,
 	LWTRANCHE_FIRST_USER_DEFINED,
 }			BuiltinTrancheIds;
 
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
index bff0f52a46..e7a00bde12 100644
--- a/src/test/recovery/t/050_verify_slot_order.pl
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -146,4 +146,131 @@ $result = $subscriber1->safe_psql('postgres',
 	"SELECT count(*) = $primary_row_count FROM tab_int;");
 is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
 
+# Test logical failover slots on the standby
+# Configure standby3 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub1_slot->| ----> subscriber1 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb3_slot--->| ----> standby3 (connected via streaming replication)
+#                                        |                 lsub1_slot(synced_slot)
+
+# Cleanup old standby_slot_names
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = ''
+));
+$primary->start;
+
+$primary->psql('postgres',
+		q{SELECT pg_create_physical_replication_slot('sb3_slot');});
+
+$backup_name = 'backup2';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+my $connstr_1 = $primary->connstr;
+$standby3->stop;
+$standby3->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb3_slot'
+});
+$standby3->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+$standby3->start;
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb3_slot'
+));
+$primary->start;
+
+# Restart the standby
+$standby3->restart;
+
+# Wait for the standby to start sync
+my $offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots;}
+  ),
+  'lsub1_slot',
+  'failover slot was created');
+
+# Verify slot properties on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|r",
+  'logical slot has sync_state as ready and failover as true on standby');
+
+# Verify slot properties on the primary
+is( $primary->safe_psql('postgres',
+    q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|n",
+  'logical slot has sync_state as none and failover as true on primary');
+
+# Test to confirm that restart_lsn of the logical slot on the primary is synced to the standby
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_int;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# let the slots get synced on the standby
+sleep 2;
+
+# Get the restart_lsn for the logical slot lsub1_slot on the primary
+my $primary_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that restart_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'restart_lsn of slot lsub1_slot synced to standby');
+
+# Get the confirmed_flush_lsn for the logical slot lsub1_slot on the primary
+$primary_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that confirmed_flush_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub1_slot synced to the standby');
+
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d80d30e99c..b559974879 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1432,6 +1432,7 @@ LimitState
 LimitStateCond
 List
 ListCell
+ListDBForLogicalSlotsCmd
 ListDictionary
 ListParsedLex
 ListenAction
@@ -1511,6 +1512,7 @@ LogicalSlotInfo
 LogicalSlotInfoArr
 LogicalTape
 LogicalTapeSet
+LogicalWorkerHeader
 LsnReadQueue
 LsnReadQueueNextFun
 LsnReadQueueNextStatus
@@ -2314,6 +2316,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2572,6 +2575,8 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorker
+SlotSyncWorkerInfo
 SlruCtl
 SlruCtlData
 SlruErrorCause
@@ -3019,6 +3024,7 @@ WalLevel
 WalRcvData
 WalRcvExecResult
 WalRcvExecStatus
+WalRcvFailoverSlotsData
 WalRcvState
 WalRcvStreamOptions
 WalRcvWakeupReason
-- 
2.30.0.windows.2



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-11-27 08:45     ` Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-11-27 08:45 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 11/27/23 7:02 AM, Zhijie Hou (Fujitsu) wrote:
> On Monday, November 27, 2023 12:03 PM Zhijie Hou (Fujitsu) <[email protected]> wrote:
>>
>> Attach the V38 patch set which addressed all comments in [1][2] except for the
>> ones that mentioned above.
>>
>> [1]
>> https://www.postgresql.org/message-id/CAHut%2BPv-yu71ogj_hRi6cCtmD5
>> 5bsyw7XTxj1Nq8yVFKpY3NDQ%40mail.gmail.com
>> [2]
>> https://www.postgresql.org/message-id/CAHut%2BPuEGX5kr0xh06yv8ndoA
>> QvDNedoec1OqOq3GMxDN6p%3D9A%40mail.gmail.com
> 
> I didn't increment the patch version, sorry for that. Attach the same patch set
> but increment the patch version to V39.

Thanks!

It looks like v39 does not contain (some / all?) the changes that have been
done in v38 [1].

For example, slot_exists_in_list() still exists in v39 while it was renamed to
validate_sync_slot() in v38.

[1]: https://www.postgresql.org/message-id/CAJpy0uD6dWUvBgy8MGdugf_Am4pLXTL_vqcwSeHO13v%2BMzc9KA%40mail.g...

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-11-27 08:50       ` shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-11-27 08:50 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Amit Kapila <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Mon, Nov 27, 2023 at 2:15 PM Drouvot, Bertrand
<[email protected]> wrote:
>
> Hi,
>
> On 11/27/23 7:02 AM, Zhijie Hou (Fujitsu) wrote:
> > On Monday, November 27, 2023 12:03 PM Zhijie Hou (Fujitsu) <[email protected]> wrote:
> >>
> >> Attach the V38 patch set which addressed all comments in [1][2] except for the
> >> ones that mentioned above.
> >>
> >> [1]
> >> https://www.postgresql.org/message-id/CAHut%2BPv-yu71ogj_hRi6cCtmD5
> >> 5bsyw7XTxj1Nq8yVFKpY3NDQ%40mail.gmail.com
> >> [2]
> >> https://www.postgresql.org/message-id/CAHut%2BPuEGX5kr0xh06yv8ndoA
> >> QvDNedoec1OqOq3GMxDN6p%3D9A%40mail.gmail.com
> >
> > I didn't increment the patch version, sorry for that. Attach the same patch set
> > but increment the patch version to V39.
>
> Thanks!
>
> It looks like v39 does not contain (some / all?) the changes that have been
> done in v38 [1].
>
> For example, slot_exists_in_list() still exists in v39 while it was renamed to
> validate_sync_slot() in v38.
>

Yes, I noticed that and informed Hou-san about this. New patches will
be posted soon with the correction. Meanwhile, please review v38
instead if you intend to review patch002 right now.  v39 is supposed
to have changes in patch001 alone.

thanks
Shveta


thanks
Shveta






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-11-27 08:57         ` Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 03:04           ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  0 siblings, 3 replies; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-11-27 08:57 UTC (permalink / raw)
  To: shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; +Cc: Peter Smith <[email protected]>; Amit Kapila <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Monday, November 27, 2023 4:51 PM shveta malik <[email protected]> wrote:
> 
> On Mon, Nov 27, 2023 at 2:15 PM Drouvot, Bertrand
> <[email protected]> wrote:
> >
> > Hi,
> >
> > On 11/27/23 7:02 AM, Zhijie Hou (Fujitsu) wrote:
> > > On Monday, November 27, 2023 12:03 PM Zhijie Hou (Fujitsu)
> <[email protected]> wrote:
> > >>
> > >> Attach the V38 patch set which addressed all comments in [1][2]
> > >> except for the ones that mentioned above.
> > >>
> > >> [1]
> > >>
> https://www.postgresql.org/message-id/CAHut%2BPv-yu71ogj_hRi6cCtmD5
> > >> 5bsyw7XTxj1Nq8yVFKpY3NDQ%40mail.gmail.com
> > >> [2]
> > >>
> https://www.postgresql.org/message-id/CAHut%2BPuEGX5kr0xh06yv8ndoA
> > >> QvDNedoec1OqOq3GMxDN6p%3D9A%40mail.gmail.com
> > >
> > > I didn't increment the patch version, sorry for that. Attach the
> > > same patch set but increment the patch version to V39.
> >
> > Thanks!
> >
> > It looks like v39 does not contain (some / all?) the changes that have
> > been done in v38 [1].
> >
> > For example, slot_exists_in_list() still exists in v39 while it was
> > renamed to
> > validate_sync_slot() in v38.
> >
> 
> Yes, I noticed that and informed Hou-san about this. New patches will be
> posted soon with the correction. Meanwhile, please review v38 instead if you
> intend to review patch002 right now.  v39 is supposed to have changes in
> patch001 alone.

Here is the updated version(v39_2) which include all the changes made in 0002.
Please use for review, and sorry for the confusion.

Best Regards,
Hou zj


Attachments:

  [application/octet-stream] v39_2-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch (8.0K, ../../OS0PR01MB57161938A348977127D56A6694BDA@OS0PR01MB5716.jpnprd01.prod.outlook.com/2-v39_2-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch)
  download | inline diff:
From 9c7f74df663c1d95e877dfd3e143e9619a724a9f Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Thu, 23 Nov 2023 12:49:05 +0530
Subject: [PATCH v39 3/3] Allow slot-sync worker to wait for the cascading
 standbys.

The GUC standby_slot_names is needed to be set on first standby
in order to allow it to wait for confirmation for cascading
standbys before updating logical 'synced' slots in slot-sync worker.
The intent is that the logical slots (synced ones) should not go
ahead of cascading standbys.

For the user created slots on first standby, we already have this wait
logic in place in logical walsender and in pg_logical_slot_get_changes_guts(),
but for synced slots (which can not be consumed yet), we need to make
sure that they are not going ahead of cascading standbys and that is
acheived by introducing the wait in slot-sync worker before we actually
update the slots.
---
 src/backend/replication/logical/slotsync.c | 86 ++++++++++++++++++++--
 src/backend/replication/walsender.c        | 11 +--
 src/include/replication/walsender.h        |  5 ++
 3 files changed, 89 insertions(+), 13 deletions(-)

diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 9aa3562500..1aac8ce648 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -83,6 +83,9 @@ typedef struct RemoteSlot
  */
 #define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
 
+static void ProcessSlotSyncInterrupts(WalReceiverConn **wrconn,
+									  List **standby_slots);
+
 /*
  * Wait for remote slot to pass locally reserved position.
  *
@@ -452,6 +455,52 @@ construct_slot_query(StringInfo s)
 					 " WHERE failover and sync_state != 'i'");
 }
 
+/*
+ * Wait for cascading physical standbys corresponding to physical slots
+ * specified in standby_slot_names GUC to confirm receiving given lsn.
+ */
+static void
+wait_for_standby_confirmation(XLogRecPtr wait_for_lsn,
+							  WalReceiverConn *wrconn)
+{
+	List	   *standby_slots;
+
+	/* Nothing to be done */
+	if (strcmp(standby_slot_names, "") == 0)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	for (;;)
+	{
+		int			rc;
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		/*
+		 * This will reload configuration and will refresh the standby_slots
+		 * as well provided standby_slot_names GUC is changed by the user.
+		 */
+		ProcessSlotSyncInterrupts(&wrconn, &standby_slots);
+
+		/*
+		 * XXX: Is waiting for 5 second before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   5000L,
+					   WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+}
+
 /*
  * Synchronize single slot to given position.
  *
@@ -723,6 +772,7 @@ synchronize_slots(WalReceiverConn *wrconn)
 	List	   *remote_slot_list = NIL;
 	MemoryContext oldctx = CurrentMemoryContext;
 	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	XLogRecPtr      max_confirmed_lsn = 0;
 	ListCell   *cell;
 	bool		slot_updated = false;
 	TimestampTz now;
@@ -775,6 +825,9 @@ synchronize_slots(WalReceiverConn *wrconn)
 		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
 		Assert(!isnull);
 
+		if (remote_slot->confirmed_lsn > max_confirmed_lsn)
+			max_confirmed_lsn = remote_slot->confirmed_lsn;
+
 		/*
 		 * It is possible to get null values for LSN and Xmin if slot is
 		 * invalidated on the primary server, so handle accordingly.
@@ -818,6 +871,17 @@ synchronize_slots(WalReceiverConn *wrconn)
 	 */
 	drop_obsolete_slots(remote_slot_list);
 
+	/*
+	 * If there are cascading standbys, wait for their confirmation before we
+	 * update synced logical slots locally.
+	 *
+	 * Instead of waiting on confirmation for lsn of each slot, let us wait
+	 * once for confirmation on max_confirmed_lsn. If that is confirmed by
+	 * each cascading standby, we are good to update all the slots.
+	 */
+	if (remote_slot_list)
+		wait_for_standby_confirmation(max_confirmed_lsn, wrconn);
+
 	/* Now sync the slots locally */
 	foreach(cell, remote_slot_list)
 	{
@@ -877,12 +941,18 @@ remote_connect(void)
  * If primary_conninfo has changed, reconnect to primary.
  */
 static void
-slotsync_reread_config(WalReceiverConn **wrconn)
+slotsync_reread_config(WalReceiverConn **wrconn, List **standby_slots)
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 
-	ConfigReloadPending = false;
-	ProcessConfigFile(PGC_SIGHUP);
+	/*
+	 * Reload configs and recreate the standby_slot_names_list if GUC
+	 * standby_slot_names changed.
+	 */
+	if (standby_slots)
+		WalSndRereadConfigAndReInitSlotList(standby_slots);
+	else
+		ProcessConfigFile(PGC_SIGHUP);
 
 	/* Exit if GUC primary_conninfo got changed, let the launcher relaunch it */
 	if (strcmp(conninfo, PrimaryConnInfo) != 0)
@@ -905,7 +975,8 @@ slotsync_reread_config(WalReceiverConn **wrconn)
  * Interrupt handler for main loop of slot sync worker.
  */
 static void
-ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
+ProcessSlotSyncInterrupts(WalReceiverConn **wrconn,
+						  List **standby_slots)
 {
 	CHECK_FOR_INTERRUPTS();
 
@@ -921,7 +992,10 @@ ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
 
 
 	if (ConfigReloadPending)
-		slotsync_reread_config(wrconn);
+	{
+		ConfigReloadPending = false;
+		slotsync_reread_config(wrconn, standby_slots);
+	}
 }
 
 /*
@@ -982,7 +1056,7 @@ ReplSlotSyncWorkerMain(Datum main_arg)
 		int			rc;
 		long		naptime;
 
-		ProcessSlotSyncInterrupts(&wrconn);
+		ProcessSlotSyncInterrupts(&wrconn, NULL /* standby_slots */ );
 
 		/* Check if got promoted */
 		if (!RecoveryInProgress())
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index edb3656cd2..db5af0b598 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1608,7 +1608,7 @@ PhysicalWakeupLogicalWalSnd(void)
  * Reload the config file and reinitialize the standby slot list if the GUC
  * standby_slot_names has changed.
  */
-static void
+void
 WalSndRereadConfigAndReInitSlotList(List **standby_slots)
 {
 	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
@@ -1631,13 +1631,12 @@ WalSndRereadConfigAndReInitSlotList(List **standby_slots)
  * This function updates the passed standby_slots list, removing any slots that
  * have already caught up to or surpassed the given wait_for_lsn.
  */
-static void
+void
 WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 {
 	ListCell   *lc;
-	List	   *standby_slots_cpy = *standby_slots;
 
-	foreach(lc, standby_slots_cpy)
+	foreach(lc, *standby_slots)
 	{
 		char	   *name = lfirst(lc);
 		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
@@ -1704,10 +1703,8 @@ WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 		if (warningfmt)
 			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
 
-		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+		*standby_slots = foreach_delete_current(*standby_slots, lc);
 	}
-
-	*standby_slots = standby_slots_cpy;
 }
 
 /*
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 1fcc22a127..c5c4714788 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -15,6 +15,7 @@
 #include <signal.h>
 
 #include "access/xlogdefs.h"
+#include "nodes/pg_list.h"
 
 /*
  * What to do with a snapshot in create replication slot command.
@@ -50,7 +51,11 @@ extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn,
+									 List **standby_slots);
 extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
+extern List *WalSndGetStandbySlots(void);
+extern void WalSndRereadConfigAndReInitSlotList(List **standby_slots);
 
 /*
  * Remember that we want to wakeup walsenders later
-- 
2.30.0.windows.2



  [application/octet-stream] v39_2-0002-Add-logical-slot-sync-capability-to-the-physical.patch (103.1K, ../../OS0PR01MB57161938A348977127D56A6694BDA@OS0PR01MB5716.jpnprd01.prod.outlook.com/3-v39_2-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From 8004acbaaceb64d4fcfdc2d429c232609b9a5efe Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Mon, 27 Nov 2023 16:47:02 +0800
Subject: [PATCH v39 2/3] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover. All the failover logical
replication slots on the primary (assuming configurations are
appropriate) are automatically created on the physical standbys and
are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The replication launcher on the physical standby starts slot-sync worker
which is then responsible to keep on syncing the logical failover slots
from the primary server.

The nap time of worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. And if
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated. If a logical slot on the primary is valid but is invalidated
on the standby due to conflict (say required rows removed on the primary),
then that slot is dropped and recreated on the standby in next sync-cycle.
It is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently).

Slots synced on the standby can be identified using 'sync_state' column of
pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but waiting for the remote slot on the
     primary server to catch up.
'r': ready for periodic syncs.
---
 doc/src/sgml/config.sgml                      |   35 +-
 doc/src/sgml/logicaldecoding.sgml             |   13 +
 doc/src/sgml/system-views.sgml                |   23 +
 src/backend/access/transam/xlogrecovery.c     |   11 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    5 +-
 .../libpqwalreceiver/libpqwalreceiver.c       |   44 +-
 src/backend/replication/logical/Makefile      |    1 +
 .../replication/logical/applyparallelworker.c |    3 +-
 src/backend/replication/logical/launcher.c    |  659 +++++++++--
 src/backend/replication/logical/logical.c     |   22 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1018 +++++++++++++++++
 src/backend/replication/logical/tablesync.c   |    5 +-
 src/backend/replication/slot.c                |   18 +-
 src/backend/replication/slotfuncs.c           |   33 +-
 src/backend/replication/walsender.c           |    2 +-
 src/backend/storage/lmgr/lwlocknames.txt      |    1 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   12 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/commands/subscriptioncmds.h       |    4 +
 src/include/nodes/replnodes.h                 |    1 -
 src/include/replication/logicallauncher.h     |    9 +-
 src/include/replication/logicalworker.h       |    1 +
 src/include/replication/slot.h                |   19 +-
 src/include/replication/walreceiver.h         |   19 +
 src/include/replication/worker_internal.h     |   63 +-
 src/test/recovery/t/050_verify_slot_order.pl  |  127 ++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    5 +
 33 files changed, 2026 insertions(+), 152 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 6440378d5c..b2da901ad6 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4357,6 +4357,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must enable
+        <varname>enable_syncslot</varname> for the standbys to receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4550,10 +4556,15 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           A password needs to be provided too, if the sender demands password
           authentication.  It can be provided in the
           <varname>primary_conninfo</varname> string, or in a separate
-          <filename>~/.pgpass</filename> file on the standby server (use
+          <filename>~/.pgpass</filename> file on the standby server. (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          Specify <literal>dbname</literal> in
+          <varname>primary_conninfo</varname> string to allow synchronization
+          of slots from the primary server to the standby server.
+          This will only be used for slot synchronization. It is ignored
+          for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4878,6 +4889,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..90bec06f36 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,19 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and set
+     <varname>enable_syncslot</varname> on the standby. The physical replication
+     slot for the standby should be listed in
+     <varname>standby_slot_names</varname> on the primary to prevent the
+     subscriber from consuming changes faster than the hot standby.
+     Additionally, similar to creating a logical replication slot on the hot
+     standby, <varname>hot_standby_feedback</varname> should be set on the
+     standby and a physical slot between the primary and the standby should be
+     used.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..f08beb83e1 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,29 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has enabled slots synchronization.
+      </para>
+      <para>
+       State code:
+       <literal>n</literal> = none for user created slots,
+       <literal>i</literal> = sync initiated for the slot but slot is not ready
+        yet for periodic syncs,
+       <literal>r</literal> = ready for periodic syncs
+      </para>
+      <para>
+      The primary server will have sync_state as 'n' for all the slots.
+      But if the standby is promoted to become the new primary server,
+      sync_state can be seen 'r' as well. On this new primary server, slots
+      with sync_state as 'r' and 'n' behaves the same.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c61566666a..8ea6dc799a 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -49,7 +49,9 @@
 #include "postmaster/bgwriter.h"
 #include "postmaster/startup.h"
 #include "replication/slot.h"
+#include "replication/logicallauncher.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1437,15 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 48a9924527..0e039c786f 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -125,11 +125,14 @@ static const struct
 		"ParallelWorkerMain", ParallelWorkerMain
 	},
 	{
-		"ApplyLauncherMain", ApplyLauncherMain
+		"LauncherMain", LauncherMain
 	},
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 336c2bec99..5f82d01e6d 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -413,6 +416,45 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	PQconninfoOption *opt;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
@@ -998,7 +1040,7 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
  */
 static void
 libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
-					 bool failover)
+					bool failover)
 {
 	StringInfoData cmd;
 	PGresult   *res;
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 9b37736f8e..192c9e1860 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -922,7 +922,8 @@ ParallelApplyWorkerMain(Datum main_arg)
 	before_shmem_exit(pa_shutdown, PointerGetDatum(seg));
 
 	SpinLockAcquire(&MyParallelShared->mutex);
-	MyParallelShared->logicalrep_worker_generation = MyLogicalRepWorker->generation;
+	MyParallelShared->logicalrep_worker_generation =
+								MyLogicalRepWorker->hdr.generation;
 	MyParallelShared->logicalrep_worker_slot_no = worker_slot;
 	SpinLockRelease(&MyParallelShared->mutex);
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 501910b445..acafb54276 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -8,20 +8,27 @@
  *	  src/backend/replication/logical/launcher.c
  *
  * NOTES
- *	  This module contains the logical replication worker launcher which
- *	  uses the background worker infrastructure to start the logical
- *	  replication workers for every enabled subscription.
+ *	  This module contains the replication worker launcher which
+ *	  uses the background worker infrastructure to:
+ *	  a) start the logical replication workers for every enabled subscription
+ *	     when not in standby_mode.
+ *	  b) start the slot sync worker for logical failover slots synchronization
+ *	     from the primary server when in standby_mode.
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
+#include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup.h"
 #include "access/htup_details.h"
 #include "access/tableam.h"
 #include "access/xact.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_database.h"
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_subscription_rel.h"
 #include "funcapi.h"
@@ -44,6 +51,7 @@
 #include "storage/procsignal.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/ps_status.h"
@@ -57,6 +65,9 @@
 int			max_logical_replication_workers = 4;
 int			max_sync_workers_per_subscription = 2;
 int			max_parallel_apply_workers_per_subscription = 2;
+bool		enable_syncslot = false;
+
+SlotSyncWorkerInfo *SlotSyncWorker = NULL;
 
 LogicalRepWorker *MyLogicalRepWorker = NULL;
 
@@ -70,6 +81,7 @@ typedef struct LogicalRepCtxStruct
 	dshash_table_handle last_start_dsh;
 
 	/* Background workers. */
+	SlotSyncWorkerInfo ss_worker;	/* slot sync worker */
 	LogicalRepWorker workers[FLEXIBLE_ARRAY_MEMBER];
 } LogicalRepCtxStruct;
 
@@ -102,6 +114,7 @@ static void logicalrep_launcher_onexit(int code, Datum arg);
 static void logicalrep_worker_onexit(int code, Datum arg);
 static void logicalrep_worker_detach(void);
 static void logicalrep_worker_cleanup(LogicalRepWorker *worker);
+static void slotsync_worker_cleanup(SlotSyncWorkerInfo *worker);
 static int	logicalrep_pa_worker_count(Oid subid);
 static void logicalrep_launcher_attach_dshmem(void);
 static void ApplyLauncherSetWorkerStartTime(Oid subid, TimestampTz start_time);
@@ -178,6 +191,8 @@ get_subscription_list(void)
 }
 
 /*
+ * This is common code for logical workers and slot sync worker.
+ *
  * Wait for a background worker to start up and attach to the shmem context.
  *
  * This is only needed for cleaning up the shared memory in case the worker
@@ -186,12 +201,14 @@ get_subscription_list(void)
  * Returns whether the attach was successful.
  */
 static bool
-WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
+WaitForReplicationWorkerAttach(LogicalWorkerHeader *worker,
 							   uint16 generation,
-							   BackgroundWorkerHandle *handle)
+							   BackgroundWorkerHandle *handle,
+							   LWLock *lock)
 {
 	BgwHandleStatus status;
 	int			rc;
+	bool		is_slotsync_worker = (lock == SlotSyncWorkerLock) ? true : false;
 
 	for (;;)
 	{
@@ -199,27 +216,32 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 
 		CHECK_FOR_INTERRUPTS();
 
-		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		LWLockAcquire(lock, LW_SHARED);
 
 		/* Worker either died or has started. Return false if died. */
 		if (!worker->in_use || worker->proc)
 		{
-			LWLockRelease(LogicalRepWorkerLock);
+			LWLockRelease(lock);
 			return worker->in_use;
 		}
 
-		LWLockRelease(LogicalRepWorkerLock);
+		LWLockRelease(lock);
 
 		/* Check if worker has died before attaching, and clean up after it. */
 		status = GetBackgroundWorkerPid(handle, &pid);
 
 		if (status == BGWH_STOPPED)
 		{
-			LWLockAcquire(LogicalRepWorkerLock, LW_EXCLUSIVE);
+			LWLockAcquire(lock, LW_EXCLUSIVE);
 			/* Ensure that this was indeed the worker we waited for. */
 			if (generation == worker->generation)
-				logicalrep_worker_cleanup(worker);
-			LWLockRelease(LogicalRepWorkerLock);
+			{
+				if (is_slotsync_worker)
+					slotsync_worker_cleanup((SlotSyncWorkerInfo *) worker);
+				else
+					logicalrep_worker_cleanup((LogicalRepWorker *) worker);
+			}
+			LWLockRelease(lock);
 			return false;
 		}
 
@@ -262,8 +284,8 @@ logicalrep_worker_find(Oid subid, Oid relid, bool only_running)
 		if (isParallelApplyWorker(w))
 			continue;
 
-		if (w->in_use && w->subid == subid && w->relid == relid &&
-			(!only_running || w->proc))
+		if (w->hdr.in_use && w->subid == subid && w->relid == relid &&
+			(!only_running || w->hdr.proc))
 		{
 			res = w;
 			break;
@@ -290,7 +312,8 @@ logicalrep_workers_find(Oid subid, bool only_running)
 	{
 		LogicalRepWorker *w = &LogicalRepCtx->workers[i];
 
-		if (w->in_use && w->subid == subid && (!only_running || w->proc))
+		if (w->hdr.in_use && w->subid == subid &&
+			(!only_running || w->hdr.proc))
 			res = lappend(res, w);
 	}
 
@@ -351,7 +374,7 @@ retry:
 	{
 		LogicalRepWorker *w = &LogicalRepCtx->workers[i];
 
-		if (!w->in_use)
+		if (!w->hdr.in_use)
 		{
 			worker = w;
 			slot = i;
@@ -380,7 +403,7 @@ retry:
 			 * If the worker was marked in use but didn't manage to attach in
 			 * time, clean it up.
 			 */
-			if (w->in_use && !w->proc &&
+			if (w->hdr.in_use && !w->hdr.proc &&
 				TimestampDifferenceExceeds(w->launch_time, now,
 										   wal_receiver_timeout))
 			{
@@ -438,9 +461,9 @@ retry:
 	/* Prepare the worker slot. */
 	worker->type = wtype;
 	worker->launch_time = now;
-	worker->in_use = true;
-	worker->generation++;
-	worker->proc = NULL;
+	worker->hdr.in_use = true;
+	worker->hdr.generation++;
+	worker->hdr.proc = NULL;
 	worker->dbid = dbid;
 	worker->userid = userid;
 	worker->subid = subid;
@@ -457,7 +480,7 @@ retry:
 	TIMESTAMP_NOBEGIN(worker->reply_time);
 
 	/* Before releasing lock, remember generation for future identification. */
-	generation = worker->generation;
+	generation = worker->hdr.generation;
 
 	LWLockRelease(LogicalRepWorkerLock);
 
@@ -510,7 +533,7 @@ retry:
 	{
 		/* Failed to start worker, so clean up the worker slot. */
 		LWLockAcquire(LogicalRepWorkerLock, LW_EXCLUSIVE);
-		Assert(generation == worker->generation);
+		Assert(generation == worker->hdr.generation);
 		logicalrep_worker_cleanup(worker);
 		LWLockRelease(LogicalRepWorkerLock);
 
@@ -522,19 +545,23 @@ retry:
 	}
 
 	/* Now wait until it attaches. */
-	return WaitForReplicationWorkerAttach(worker, generation, bgw_handle);
+	return WaitForReplicationWorkerAttach((LogicalWorkerHeader *) worker,
+										  generation,
+										  bgw_handle,
+										  LogicalRepWorkerLock);
 }
 
 /*
  * Internal function to stop the worker and wait until it detaches from the
- * slot.
+ * slot. It is used for both logical workers and slot sync worker.
  */
 static void
-logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
+logicalrep_worker_stop_internal(LogicalWorkerHeader *worker, int signo,
+								LWLock *lock)
 {
 	uint16		generation;
 
-	Assert(LWLockHeldByMeInMode(LogicalRepWorkerLock, LW_SHARED));
+	Assert(LWLockHeldByMeInMode(lock, LW_SHARED));
 
 	/*
 	 * Remember which generation was our worker so we can check if what we see
@@ -550,7 +577,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 	{
 		int			rc;
 
-		LWLockRelease(LogicalRepWorkerLock);
+		LWLockRelease(lock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
 		rc = WaitLatch(MyLatch,
@@ -564,7 +591,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		}
 
 		/* Recheck worker status. */
-		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		LWLockAcquire(lock, LW_SHARED);
 
 		/*
 		 * Check whether the worker slot is no longer used, which would mean
@@ -591,7 +618,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		if (!worker->proc || worker->generation != generation)
 			break;
 
-		LWLockRelease(LogicalRepWorkerLock);
+		LWLockRelease(lock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
 		rc = WaitLatch(MyLatch,
@@ -604,7 +631,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 			CHECK_FOR_INTERRUPTS();
 		}
 
-		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		LWLockAcquire(lock, LW_SHARED);
 	}
 }
 
@@ -623,7 +650,9 @@ logicalrep_worker_stop(Oid subid, Oid relid)
 	if (worker)
 	{
 		Assert(!isParallelApplyWorker(worker));
-		logicalrep_worker_stop_internal(worker, SIGTERM);
+		logicalrep_worker_stop_internal((LogicalWorkerHeader *) worker,
+										SIGTERM,
+										LogicalRepWorkerLock);
 	}
 
 	LWLockRelease(LogicalRepWorkerLock);
@@ -669,8 +698,10 @@ logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
 	/*
 	 * Only stop the worker if the generation matches and the worker is alive.
 	 */
-	if (worker->generation == generation && worker->proc)
-		logicalrep_worker_stop_internal(worker, SIGINT);
+	if (worker->hdr.generation == generation && worker->hdr.proc)
+		logicalrep_worker_stop_internal((LogicalWorkerHeader *) worker,
+										SIGINT,
+										LogicalRepWorkerLock);
 
 	LWLockRelease(LogicalRepWorkerLock);
 }
@@ -696,14 +727,14 @@ logicalrep_worker_wakeup(Oid subid, Oid relid)
 /*
  * Wake up (using latch) the specified logical replication worker.
  *
- * Caller must hold lock, else worker->proc could change under us.
+ * Caller must hold lock, else worker->hdr.proc could change under us.
  */
 void
 logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
 {
 	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
 
-	SetLatch(&worker->proc->procLatch);
+	SetLatch(&worker->hdr.proc->procLatch);
 }
 
 /*
@@ -718,7 +749,7 @@ logicalrep_worker_attach(int slot)
 	Assert(slot >= 0 && slot < max_logical_replication_workers);
 	MyLogicalRepWorker = &LogicalRepCtx->workers[slot];
 
-	if (!MyLogicalRepWorker->in_use)
+	if (!MyLogicalRepWorker->hdr.in_use)
 	{
 		LWLockRelease(LogicalRepWorkerLock);
 		ereport(ERROR,
@@ -727,7 +758,7 @@ logicalrep_worker_attach(int slot)
 						slot)));
 	}
 
-	if (MyLogicalRepWorker->proc)
+	if (MyLogicalRepWorker->hdr.proc)
 	{
 		LWLockRelease(LogicalRepWorkerLock);
 		ereport(ERROR,
@@ -736,7 +767,7 @@ logicalrep_worker_attach(int slot)
 						"another worker, cannot attach", slot)));
 	}
 
-	MyLogicalRepWorker->proc = MyProc;
+	MyLogicalRepWorker->hdr.proc = MyProc;
 	before_shmem_exit(logicalrep_worker_onexit, (Datum) 0);
 
 	LWLockRelease(LogicalRepWorkerLock);
@@ -771,7 +802,9 @@ logicalrep_worker_detach(void)
 			LogicalRepWorker *w = (LogicalRepWorker *) lfirst(lc);
 
 			if (isParallelApplyWorker(w))
-				logicalrep_worker_stop_internal(w, SIGTERM);
+				logicalrep_worker_stop_internal((LogicalWorkerHeader *) w,
+												SIGTERM,
+												LogicalRepWorkerLock);
 		}
 
 		LWLockRelease(LogicalRepWorkerLock);
@@ -794,10 +827,10 @@ logicalrep_worker_cleanup(LogicalRepWorker *worker)
 	Assert(LWLockHeldByMeInMode(LogicalRepWorkerLock, LW_EXCLUSIVE));
 
 	worker->type = WORKERTYPE_UNKNOWN;
-	worker->in_use = false;
-	worker->proc = NULL;
-	worker->dbid = InvalidOid;
+	worker->hdr.in_use = false;
+	worker->hdr.proc = NULL;
 	worker->userid = InvalidOid;
+	worker->dbid = InvalidOid;
 	worker->subid = InvalidOid;
 	worker->relid = InvalidOid;
 	worker->leader_pid = InvalidPid;
@@ -931,9 +964,18 @@ ApplyLauncherRegister(void)
 	memset(&bgw, 0, sizeof(bgw));
 	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
 		BGWORKER_BACKEND_DATABASE_CONNECTION;
-	bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
+
+	/*
+	 * The launcher now takes care of launching both logical apply workers and
+	 * logical slot sync worker. Thus to cater to the requirements of both,
+	 * start it as soon as a consistent state is reached. This will help
+	 * slot sync worker to start timely on a physical standby while on a
+	 * non-standby server, it holds same meaning as that of
+	 * BgWorkerStart_RecoveryFinished.
+	 */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState;
 	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
-	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ApplyLauncherMain");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "LauncherMain");
 	snprintf(bgw.bgw_name, BGW_MAXLEN,
 			 "logical replication launcher");
 	snprintf(bgw.bgw_type, BGW_MAXLEN,
@@ -1115,13 +1157,447 @@ ApplyLauncherWakeup(void)
 }
 
 /*
- * Main loop for the apply launcher process.
+ * Clean up slot sync worker info.
+ */
+static void
+slotsync_worker_cleanup(SlotSyncWorkerInfo *worker)
+{
+	Assert(LWLockHeldByMeInMode(SlotSyncWorkerLock, LW_EXCLUSIVE));
+
+	worker->hdr.in_use = false;
+	worker->hdr.proc = NULL;
+	worker->last_update_time = 0;
+}
+
+/*
+ * Attach slot sync worker to SlotSyncWorkerInfo assigned by the launcher.
  */
 void
-ApplyLauncherMain(Datum main_arg)
+slotsync_worker_attach()
 {
-	ereport(DEBUG1,
-			(errmsg_internal("logical replication launcher started")));
+	/* Block concurrent access. */
+	LWLockAcquire(SlotSyncWorkerLock, LW_EXCLUSIVE);
+
+	SlotSyncWorker = &LogicalRepCtx->ss_worker;
+
+	if (!SlotSyncWorker->hdr.in_use)
+	{
+		LWLockRelease(SlotSyncWorkerLock);
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("replication slot sync worker not initialized, cannot attach")));
+	}
+
+	if (SlotSyncWorker->hdr.proc)
+	{
+		LWLockRelease(SlotSyncWorkerLock);
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("replication slot sync worker is already running, cannot attach")));
+	}
+
+	SlotSyncWorker->hdr.proc = MyProc;
+
+	before_shmem_exit(slotsync_worker_detach, (Datum) 0);
+
+	LWLockRelease(SlotSyncWorkerLock);
+}
+
+/*
+ * Detach the slot sync worker (cleans up the worker info).
+ */
+void
+slotsync_worker_detach(int code, Datum arg)
+{
+	LWLockAcquire(SlotSyncWorkerLock, LW_EXCLUSIVE);
+	slotsync_worker_cleanup(SlotSyncWorker);
+	LWLockRelease(SlotSyncWorkerLock);
+}
+
+/*
+ * Start slot sync background worker.
+ *
+ * Returns true on success, false on failure.
+ */
+static bool
+slotsync_worker_launch()
+{
+	BackgroundWorker bgw;
+	BackgroundWorkerHandle *bgw_handle;
+	SlotSyncWorkerInfo *worker;
+	bool		attach;
+
+	/* The shared memory must only be modified under lock. */
+	LWLockAcquire(SlotSyncWorkerLock, LW_EXCLUSIVE);
+
+	SlotSyncWorker = &LogicalRepCtx->ss_worker;
+
+	worker = SlotSyncWorker;
+
+	/* Prepare the new worker. */
+	worker->hdr.in_use = true;
+
+	/*
+	 * 'proc' will be assigned in ReplSlotSyncWorkerMain when the worker
+	 * attaches to SlotSyncWorkerInfo.
+	 */
+	worker->hdr.proc = NULL;
+
+	LWLockRelease(SlotSyncWorkerLock);
+
+	/* Register the new dynamic worker. */
+	memset(&bgw, 0, sizeof(bgw));
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState;
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+
+	snprintf(bgw.bgw_name, BGW_MAXLEN, "replication slot sync worker");
+
+	snprintf(bgw.bgw_type, BGW_MAXLEN, "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_NEVER_RESTART;
+	bgw.bgw_notify_pid = MyProcPid;
+
+	if (!RegisterDynamicBackgroundWorker(&bgw, &bgw_handle))
+	{
+		/* Failed to start worker, so clean up the worker slot. */
+		LWLockAcquire(SlotSyncWorkerLock, LW_EXCLUSIVE);
+		slotsync_worker_cleanup(worker);
+		LWLockRelease(SlotSyncWorkerLock);
+
+		ereport(WARNING,
+				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+				 errmsg("out of background worker slots"),
+				 errhint("You might need to increase %s.",
+						 "max_worker_processes")));
+		return false;
+	}
+
+	/* Now wait until it attaches. */
+	attach = WaitForReplicationWorkerAttach((LogicalWorkerHeader *) worker,
+											worker->hdr.generation,
+											bgw_handle,
+											SlotSyncWorkerLock);
+
+	if (!attach)
+		ereport(WARNING,
+				(errmsg("replication slot sync worker failed to attach")));
+
+	return attach;
+}
+
+/*
+ * Internal function to stop the slot sync worker and cleanup afterwards.
+ */
+static void
+slotsync_worker_stop_internal(SlotSyncWorkerInfo *worker)
+{
+	LWLockAcquire(SlotSyncWorkerLock, LW_SHARED);
+	ereport(LOG,
+			(errmsg("stopping replication slot sync worker with pid: %d",
+					worker->hdr.proc->pid)));
+	logicalrep_worker_stop_internal((LogicalWorkerHeader *) worker,
+									SIGINT,
+									SlotSyncWorkerLock);
+	LWLockRelease(SlotSyncWorkerLock);
+}
+
+/*
+ * Validate if db with given dbname exists.
+ *
+ * Can't use existing functions like 'get_database_oid' from dbcommands.c for
+ * validity purpose as they need db connection.
+ */
+static bool
+validate_dbname(const char *dbname)
+{
+	HeapTuple	tuple;
+	Relation	relation;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	bool		valid;
+
+	/* Start a transaction so we can access pg_database */
+	StartTransactionCommand();
+
+	/* Form a scan key */
+	ScanKeyInit(&key[0], Anum_pg_database_datname, BTEqualStrategyNumber,
+				F_NAMEEQ, CStringGetDatum(dbname));
+
+	/* No db connection, force heap scan */
+	relation = table_open(DatabaseRelationId, AccessShareLock);
+	scan = systable_beginscan(relation, DatabaseNameIndexId, false,
+							  NULL, 1, key);
+
+	tuple = systable_getnext(scan);
+
+	if (HeapTupleIsValid(tuple))
+		valid = true;
+	else
+		valid = false;
+
+	/* all done */
+	systable_endscan(scan);
+	table_close(relation, AccessShareLock);
+
+	CommitTransactionCommand();
+	return valid;
+}
+
+/*
+ * Checks if GUC are set appropriately before starting slot sync worker
+ */
+static bool
+slotsync_checks(long *wait_time, bool *retry)
+{
+	char	   *dbname;
+
+	*retry = false;
+
+	if (!enable_syncslot)
+		return false;
+
+	/*
+	 * Since the above GUC is set, check that other GUC settings
+	 * (primary_slot_name, hot_standby_feedback, primary_conninfo, wal_level)
+	 * are compatible with slot synchronization. If not, issue warnings.
+	 */
+
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (!WalRcv || WalRcv->slotname[0] == '\0')
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronization as primary_slot_name is not set"));
+
+		/*
+		 * It's possible that the Walreceiver has not been started yet, adjust
+		 * the wait_time to retry sooner in the next synchronization cycle.
+		 */
+		*wait_time = wal_retrieve_retry_interval;
+
+		/*
+		 * Tell caller to retry the connection for the case where
+		 * primary_slot_name is set but Walreceiver is not yet started.
+		 */
+		if (PrimarySlotName && strcmp(PrimarySlotName, "") != 0)
+			*retry = true;
+
+		return false;
+	}
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronization as hot_standby_feedback is off"));
+		return false;
+	}
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronisation as it requires wal_level >= logical"));
+		return false;
+	}
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (dbname == NULL)
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronization as dbname is not specified in primary_conninfo"));
+		return false;
+	}
+
+	if (!validate_dbname(dbname))
+	{
+		ereport(WARNING,
+				errmsg("skipping slots synchronization as dbname specified in primary_conninfo is not a valid one"));
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	if (LogicalRepCtx->ss_worker.hdr.in_use)
+		slotsync_worker_stop_internal(&LogicalRepCtx->ss_worker);
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If one of the slot sync options has changed, stop the slot sync worker
+ * and set ss_recheck flag to enable the caller to recheck slot sync GUCs
+ * before restarting the worker
+ */
+static void
+LauncherRereadConfig(bool *ss_recheck)
+{
+	char	   *conninfo = pstrdup(PrimaryConnInfo);
+	char	   *slotname = pstrdup(PrimarySlotName);
+	bool		syncslot = enable_syncslot;
+	bool		standbyfeedback = hot_standby_feedback;
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	/*
+	 * If any of the related GUCs changed, stop the slot sync worker. The
+	 * worker will be relaunched in next sync-cycle using the new GUCs.
+	 */
+	if ((strcmp(conninfo, PrimaryConnInfo) != 0) ||
+		(strcmp(slotname, PrimarySlotName) != 0) ||
+		(syncslot != enable_syncslot) ||
+		(standbyfeedback != hot_standby_feedback))
+	{
+		ShutDownSlotSync();
+
+		/* Retry slot sync with new GUCs */
+		*ss_recheck = true;
+	}
+
+	pfree(conninfo);
+	pfree(slotname);
+}
+
+/*
+ * Launch slot sync background worker.
+ */
+static void
+LaunchSlotSyncWorker(long *wait_time)
+{
+	LWLockAcquire(SlotSyncWorkerLock, LW_SHARED);
+
+	/* The worker is running already */
+	if (SlotSyncWorker && SlotSyncWorker->hdr.in_use &&
+		SlotSyncWorker->hdr.proc)
+	{
+		LWLockRelease(SlotSyncWorkerLock);
+		return;
+	}
+
+	LWLockRelease(SlotSyncWorkerLock);
+
+	/*
+	 * If launch failed, adjust the wait_time to retry in the next sync-cycle
+	 * sooner.
+	 */
+	if (!slotsync_worker_launch())
+	{
+		*wait_time = Min(*wait_time, wal_retrieve_retry_interval);
+	}
+}
+
+/*
+ * Launch logical replication apply workers for enabled subscriptions.
+ */
+static void
+LaunchSubscriptionApplyWorker(long *wait_time)
+{
+	List	   *sublist;
+	ListCell   *lc;
+	MemoryContext subctx;
+	MemoryContext oldctx;
+
+	/* Use temporary context to avoid leaking memory across cycles. */
+	subctx = AllocSetContextCreate(TopMemoryContext,
+								   "Logical Replication Launcher sublist",
+								   ALLOCSET_DEFAULT_SIZES);
+	oldctx = MemoryContextSwitchTo(subctx);
+
+	/* Start any missing workers for enabled subscriptions. */
+	sublist = get_subscription_list();
+	foreach(lc, sublist)
+	{
+		Subscription *sub = (Subscription *) lfirst(lc);
+		LogicalRepWorker *w;
+		TimestampTz last_start;
+		TimestampTz now;
+		long		elapsed;
+
+		if (!sub->enabled)
+			continue;
+
+		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		w = logicalrep_worker_find(sub->oid, InvalidOid, false);
+		LWLockRelease(LogicalRepWorkerLock);
+
+		if (w != NULL)
+			continue;			/* worker is running already */
+
+		/*
+		 * If the worker is eligible to start now, launch it.  Otherwise,
+		 * adjust wait_time so that we'll wake up as soon as it can be
+		 * started.
+		 *
+		 * Each subscription's apply worker can only be restarted once per
+		 * wal_retrieve_retry_interval, so that errors do not cause us to
+		 * repeatedly restart the worker as fast as possible.  In cases where
+		 * a restart is expected (e.g., subscription parameter changes),
+		 * another process should remove the last-start entry for the
+		 * subscription so that the worker can be restarted without waiting
+		 * for wal_retrieve_retry_interval to elapse.
+		 */
+		last_start = ApplyLauncherGetWorkerStartTime(sub->oid);
+		now = GetCurrentTimestamp();
+		if (last_start == 0 ||
+			(elapsed = TimestampDifferenceMilliseconds(last_start, now)) >=
+			wal_retrieve_retry_interval)
+		{
+			ApplyLauncherSetWorkerStartTime(sub->oid, now);
+			logicalrep_worker_launch(WORKERTYPE_APPLY,
+									 sub->dbid, sub->oid, sub->name,
+									 sub->owner, InvalidOid,
+									 DSM_HANDLE_INVALID);
+		}
+		else
+		{
+			*wait_time = Min(*wait_time,
+							 wal_retrieve_retry_interval - elapsed);
+		}
+	}
+
+	/* Switch back to original memory context. */
+	MemoryContextSwitchTo(oldctx);
+	/* Clean the temporary memory. */
+	MemoryContextDelete(subctx);
+}
+
+/*
+ * Main loop for the launcher process.
+ */
+void
+LauncherMain(Datum main_arg)
+{
+	bool		start_slotsync = false;
+	bool		recheck_slotsync = true;
+
+	elog(DEBUG1, "logical replication launcher started");
 
 	before_shmem_exit(logicalrep_launcher_onexit, (Datum) 0);
 
@@ -1139,79 +1615,32 @@ ApplyLauncherMain(Datum main_arg)
 	 */
 	BackgroundWorkerInitializeConnection(NULL, NULL, 0);
 
+	load_file("libpqwalreceiver", false);
+
 	/* Enter main loop */
 	for (;;)
 	{
 		int			rc;
-		List	   *sublist;
-		ListCell   *lc;
-		MemoryContext subctx;
-		MemoryContext oldctx;
 		long		wait_time = DEFAULT_NAPTIME_PER_CYCLE;
 
 		CHECK_FOR_INTERRUPTS();
 
-		/* Use temporary context to avoid leaking memory across cycles. */
-		subctx = AllocSetContextCreate(TopMemoryContext,
-									   "Logical Replication Launcher sublist",
-									   ALLOCSET_DEFAULT_SIZES);
-		oldctx = MemoryContextSwitchTo(subctx);
-
-		/* Start any missing workers for enabled subscriptions. */
-		sublist = get_subscription_list();
-		foreach(lc, sublist)
+		/*
+		 * If it is Hot standby, then try to launch slot sync worker else
+		 * launch apply workers.
+		 */
+		if (RecoveryInProgress() && !PromoteIsTriggered())
 		{
-			Subscription *sub = (Subscription *) lfirst(lc);
-			LogicalRepWorker *w;
-			TimestampTz last_start;
-			TimestampTz now;
-			long		elapsed;
-
-			if (!sub->enabled)
-				continue;
-
-			LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
-			w = logicalrep_worker_find(sub->oid, InvalidOid, false);
-			LWLockRelease(LogicalRepWorkerLock);
+			/* Make validation checks first */
+			if (recheck_slotsync)
+				start_slotsync = slotsync_checks(&wait_time, &recheck_slotsync);
 
-			if (w != NULL)
-				continue;		/* worker is running already */
-
-			/*
-			 * If the worker is eligible to start now, launch it.  Otherwise,
-			 * adjust wait_time so that we'll wake up as soon as it can be
-			 * started.
-			 *
-			 * Each subscription's apply worker can only be restarted once per
-			 * wal_retrieve_retry_interval, so that errors do not cause us to
-			 * repeatedly restart the worker as fast as possible.  In cases
-			 * where a restart is expected (e.g., subscription parameter
-			 * changes), another process should remove the last-start entry
-			 * for the subscription so that the worker can be restarted
-			 * without waiting for wal_retrieve_retry_interval to elapse.
-			 */
-			last_start = ApplyLauncherGetWorkerStartTime(sub->oid);
-			now = GetCurrentTimestamp();
-			if (last_start == 0 ||
-				(elapsed = TimestampDifferenceMilliseconds(last_start, now)) >= wal_retrieve_retry_interval)
-			{
-				ApplyLauncherSetWorkerStartTime(sub->oid, now);
-				logicalrep_worker_launch(WORKERTYPE_APPLY,
-										 sub->dbid, sub->oid, sub->name,
-										 sub->owner, InvalidOid,
-										 DSM_HANDLE_INVALID);
-			}
-			else
-			{
-				wait_time = Min(wait_time,
-								wal_retrieve_retry_interval - elapsed);
-			}
+			/* Start slot sync workers if checks passed */
+			if (start_slotsync)
+				LaunchSlotSyncWorker(&wait_time);
 		}
-
-		/* Switch back to original memory context. */
-		MemoryContextSwitchTo(oldctx);
-		/* Clean the temporary memory. */
-		MemoryContextDelete(subctx);
+		else
+			LaunchSubscriptionApplyWorker(&wait_time);
 
 		/* Wait for more work. */
 		rc = WaitLatch(MyLatch,
@@ -1226,10 +1655,7 @@ ApplyLauncherMain(Datum main_arg)
 		}
 
 		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
-		}
+			LauncherRereadConfig(&recheck_slotsync);
 	}
 
 	/* Not reachable */
@@ -1260,7 +1686,8 @@ GetLeaderApplyWorkerPid(pid_t pid)
 	{
 		LogicalRepWorker *w = &LogicalRepCtx->workers[i];
 
-		if (isParallelApplyWorker(w) && w->proc && pid == w->proc->pid)
+		if (isParallelApplyWorker(w) && w->hdr.proc &&
+			pid == w->hdr.proc->pid)
 		{
 			leader_pid = w->leader_pid;
 			break;
@@ -1298,13 +1725,13 @@ pg_stat_get_subscription(PG_FUNCTION_ARGS)
 
 		memcpy(&worker, &LogicalRepCtx->workers[i],
 			   sizeof(LogicalRepWorker));
-		if (!worker.proc || !IsBackendPid(worker.proc->pid))
+		if (!worker.hdr.proc || !IsBackendPid(worker.hdr.proc->pid))
 			continue;
 
 		if (OidIsValid(subid) && worker.subid != subid)
 			continue;
 
-		worker_pid = worker.proc->pid;
+		worker_pid = worker.hdr.proc->pid;
 
 		values[0] = ObjectIdGetDatum(worker.subid);
 		if (isTablesyncWorker(&worker))
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..5bef0138b4 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,28 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	/*
+	 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+	 * promotion.
+	 */
+	if (!RecoveryInProgress() && slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+		elog(ERROR, "replication slot \"%s\" was not synced completely from the primary server",
+			 NameStr(slot->data.name));
+
+	/*
+	 * Do not allow consumption of a "synchronized" slot until the standby
+	 * gets promoted. Also do not allow consumption of slots with sync_state
+	 * as SYNCSLOT_STATE_INITIATED as they are not synced completely to be
+	 * used.
+	 */
+	if (RecoveryInProgress() && slot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot use replication slot \"%s\" for logical decoding",
+						NameStr(slot->data.name)),
+				 errdetail("This slot is being synced from the primary server."),
+				 errhint("Specify another replication slot.")));
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..9aa3562500
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1018 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * It also takes care of dropping the slots which were created by it and are
+ * currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/xlogrecovery.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+/*
+ * Number of attempts for wait_for_primary_slot_catchup() after
+ * which it aborts the wait and the slot sync worker then moves
+ * to the next slot creation/sync.
+ */
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * *persist will be set to false if the slot has disappeared or was invalidated
+ * on the primary; otherwise, it will be set to true.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *persist)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+	if (persist)
+		*persist = true;
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn, confirmed_flush_lsn,"
+					 " catalog_xmin FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		XLogRecPtr	new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *slot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		Assert(RecoveryInProgress());
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot info for slot \"%s\" from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" disappeared from the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(slot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" invalidated on the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			ExecClearTuple(slot);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * Once we got valid restart_lsn, then confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(slot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\" and moving"
+						   " to the next slot, will attempt creating it again",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up.
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			slots = lappend(slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(slots);
+}
+
+/*
+ * Get list of local logical slot names which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slot_names(void)
+{
+	List	   *localSyncedSlots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			localSyncedSlots = lappend(localSyncedSlots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return localSyncedSlots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+validate_sync_slot(ReplicationSlot *local_slot, List *remote_slots,
+				   bool *locally_invalidated)
+{
+	ListCell   *cell;
+
+	foreach(cell, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Also drop the slots that are valid on the primary that got invalidated
+ * on the standby due to conflict (say required rows removed on the primary).
+ * The assumption is, that these will get recreated in next sync-cycle and
+ * it is okay to drop and recreate such slots as long as these are not
+ * consumable on the standby (which is the case currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slot_list = NIL;
+	ListCell   *lc_slot;
+
+	/*
+	 * Get the list of local 'synced' slot so that those not on remote could
+	 * be dropped.
+	 */
+	local_slot_list = get_local_synced_slot_names();
+
+	foreach(lc_slot, local_slot_list)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc_slot);
+		bool		local_exists = false;
+		bool		locally_invalidated = false;
+
+		local_exists = validate_sync_slot(local_slot, remote_slot_list,
+										  &locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!local_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Constructs the query in order to get failover logical slots
+ * information from the primary server.
+ */
+static void
+construct_slot_query(StringInfo s)
+{
+	/*
+	 * Fetch slots with failover enabled.
+	 *
+	 * If we are on cascading standby, we should fetch only those slots from
+	 * the first standby which have sync_state as either 'n' or 'r'. Slots
+	 * with sync_state as 'i' are not sync ready yet. And when we are on the
+	 * first standby, the primary server is supposed to have slots with
+	 * sync_state as 'n' only (or it may have all 'n', 'r' and 'i' if standby
+	 * is promoted as primary). Thus in all the cases, filter sync_state !='i'
+	 * is appropriate one.
+	 */
+	appendStringInfo(s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover and sync_state != 'i'");
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ */
+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+					 bool *slot_updated)
+{
+	ReplicationSlot *s;
+	char		sync_state = 0;
+
+	/*
+	 * Make sure that concerned WAL is received before syncing slot to target
+	 * lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+
+		return;
+	}
+
+	/* Search for the named slot */
+	if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		SpinLockAcquire(&s->mutex);
+		sync_state = s->data.sync_state;
+		SpinLockRelease(&s->mutex);
+	}
+
+	StartTransactionCommand();
+
+	/*
+	 * Already existing slot (created by slot sync worker) and ready for sync,
+	 * acquire and sync it.
+	 */
+	if (sync_state == SYNCSLOT_STATE_READY)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&MyReplicationSlot->mutex);
+			MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&MyReplicationSlot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * With hot_standby_feedback enabled and invalidations handled
+		 * apropriately as above, this should never happen.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn)
+		{
+			ereport(ERROR,
+					errmsg("not synchronizing local slot \"%s\" LSN(%X/%X)"
+						   " to remote slot's LSN(%X/%X) as synchronization "
+						   " would move it backwards", remote_slot->name,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   LSN_FORMAT_ARGS(remote_slot->restart_lsn)));
+
+			goto cleanup;
+		}
+
+		if (remote_slot->confirmed_lsn != MyReplicationSlot->data.confirmed_flush ||
+			remote_slot->restart_lsn != MyReplicationSlot->data.restart_lsn ||
+			remote_slot->catalog_xmin != MyReplicationSlot->data.catalog_xmin)
+		{
+			/* Update LSN of slot to remote slot's current position */
+			local_slot_update(remote_slot);
+			ReplicationSlotSave();
+			*slot_updated = true;
+		}
+	}
+
+	/*
+	 * Already existing slot but not ready (i.e. waiting for the primary
+	 * server to catch-up), lets attempt to make it sync-ready now.
+	 */
+	else if (sync_state == SYNCSLOT_STATE_INITIATED)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * Refer the slot creation part (last 'else' block) for more details
+		 * on this wait.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+			{
+				goto cleanup;
+			}
+		}
+
+		/*
+		 * Wait for primary is over, update the lsns and mark the slot as
+		 * READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Save the changes */
+		ReplicationSlotMarkDirty();
+		ReplicationSlotSave();
+
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready now",
+							remote_slot->name));
+	}
+	/* User created slot with the same name exists, raise ERROR. */
+	else if (sync_state == SYNCSLOT_STATE_NONE)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("skipping sync of slot \"%s\" as it is a user created"
+						" slot", remote_slot->name),
+				 errdetail("This slot has failover enabled on the primary and"
+						   " thus is sync candidate but user created slot with"
+						   " the same name already exists on the standby")));
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+		ReplicationSlot *slot;
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover);
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * If the local restart_lsn and/or local catalog_xmin is ahead of
+		 * those on the remote then we cannot create the local slot in sync
+		 * with the primary server because that would mean moving the local
+		 * slot backwards and we might not have WALs retained for old LSN. In
+		 * this case we will wait for the primary server's restart_lsn and
+		 * catalog_xmin to catch up with the local one before attempting the
+		 * sync.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			bool		persist;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &persist))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid) and attempt the wait and synchronization in
+				 * the next cycle.
+				 */
+				if (persist)
+				{
+					/* Mark it as sync initiated */
+					SpinLockAcquire(&MyReplicationSlot->mutex);
+					slot->data.sync_state = SYNCSLOT_STATE_INITIATED;
+					SpinLockRelease(&MyReplicationSlot->mutex);
+
+					ReplicationSlotPersist();
+					*slot_updated = true;
+				}
+
+				goto cleanup;
+			}
+		}
+
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready now",
+							remote_slot->name));
+	}
+
+cleanup:
+
+	ReplicationSlotRelease();
+	CommitTransactionCommand();
+
+	return;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and update
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns nap time for the next sync-cycle.
+ */
+static long
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *slot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	MemoryContext oldctx = CurrentMemoryContext;
+	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	ListCell   *cell;
+	bool		slot_updated = false;
+	TimestampTz now;
+
+	/* The primary_slot_name is not set yet or WALs not received yet */
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+		return naptime;
+
+	/* The syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	/*
+	 * Make result tuples live outside TopTransactionContext to make them
+	 * accessible even after transaction is committed.
+	 */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct query to get slots info from the primary server */
+	initStringInfo(&s);
+	construct_slot_query(&s);
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(slot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+																	   5, &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(slot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(slot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(slot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(slot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(slot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(cell, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		synchronize_one_slot(wrconn, remote_slot, &slot_updated);
+	}
+
+	now = GetCurrentTimestamp();
+
+	/*
+	 * If any of the slots get updated in this sync-cycle, retain default
+	 * naptime and update 'last_update_time' in slot sync worker. But if no
+	 * activity is observed in this sync-cycle, then increase naptime provided
+	 * inactivity time reaches threshold.
+	 */
+	if (slot_updated)
+		SlotSyncWorker->last_update_time = now;
+
+	else if (TimestampDifferenceExceeds(SlotSyncWorker->last_update_time,
+										now, WORKER_INACTIVITY_THRESHOLD_MS))
+		naptime = WORKER_INACTIVITY_NAPTIME_MS;
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	return naptime;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker", &err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If primary_conninfo has changed, reconnect to primary.
+ */
+static void
+slotsync_reread_config(WalReceiverConn **wrconn)
+{
+	char	   *conninfo = pstrdup(PrimaryConnInfo);
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	/* Exit if GUC primary_conninfo got changed, let the launcher relaunch it */
+	if (strcmp(conninfo, PrimaryConnInfo) != 0)
+	{
+		if (*wrconn)
+			walrcv_disconnect(*wrconn);
+
+		pfree(conninfo);
+
+		ereport(LOG,
+				(errmsg("replication slot sync worker will stop because the "
+						"primary_conninfo has changed")));
+		proc_exit(0);
+	}
+
+	pfree(conninfo);
+}
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting"
+					   " down on receiving SIGINT"));
+
+		walrcv_disconnect(*wrconn);
+		proc_exit(0);
+	}
+
+
+	if (ConfigReloadPending)
+		slotsync_reread_config(wrconn);
+}
+
+/*
+ * The main loop of our worker process.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	slotsync_worker_attach();
+
+	/*
+	 * If the standby has been promoted, skip the slot synchronization
+	 * process.
+	 *
+	 * Although the startup process stops all the slot sync workers on
+	 * promotion, the launcher may not have realized the promotion and could
+	 * start additional workers after that. Therefore, this check is still
+	 * necessary to prevent these additional workers from running.
+	 */
+	if (PromoteIsTriggered())
+		exit(0);
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	/*
+	 * Get the user provided dbname from the connection string, if dbname not
+	 * provided, skip sync.
+	 */
+	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (dbname == NULL)
+		proc_exit(0);
+
+	/*
+	 * Connect to the database specified by user in PrimaryConnInfo. We need a
+	 * database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime;
+
+		ProcessSlotSyncInterrupts(&wrconn);
+
+		/* Check if got promoted */
+		if (!RecoveryInProgress())
+		{
+			/*
+			 * Drop the slots for which sync is initiated but not yet
+			 * completed i.e. they are still waiting for the primary server to
+			 * catch up.
+			 */
+			slotsync_drop_initiated_slots();
+			ereport(LOG,
+					errmsg("exiting slot sync woker on promotion of standby"));
+			proc_exit(0);
+		}
+
+		naptime = synchronize_slots(wrconn);
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 1eaee4197b..73aacef25f 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "commands/subscriptioncmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
@@ -246,7 +247,7 @@ wait_for_worker_state_change(char expected_state)
 		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
 		worker = logicalrep_worker_find(MyLogicalRepWorker->subid,
 										InvalidOid, false);
-		if (worker && worker->proc)
+		if (worker && worker->hdr.proc)
 			logicalrep_worker_wakeup_ptr(worker);
 		LWLockRelease(LogicalRepWorkerLock);
 		if (!worker)
@@ -535,7 +536,7 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 				if (rstate->state == SUBREL_STATE_SYNCWAIT)
 				{
 					/* Signal the sync worker, as it may be waiting for us. */
-					if (syncworker->proc)
+					if (syncworker->hdr.proc)
 						logicalrep_worker_wakeup_ptr(syncworker);
 
 					/* Now safe to release the LWLock */
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index c96a1700cd..2c059dffca 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -325,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = SYNCSLOT_STATE_NONE;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -684,12 +686,26 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+	{
+		ReplicationSlotRelease();
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary.")));
+	}
+
 	ReplicationSlotDropAcquired();
 }
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index fb6e37d2c3..7b1e0c1552 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3e44228bde..edb3656cd2 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, false);
 }
 
 /*
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index f72f2906ce..e62a3f1bc0 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -54,3 +54,4 @@ XactTruncationLock					44
 WrapLimitsVacuumLock				46
 NotifyQueueTailLock					47
 WaitEventExtensionLock				48
+SlotSyncWorkerLock					49
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index e5e7bb23f9..65a1e544c1 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -65,8 +65,11 @@
 #include "postmaster/syslogger.h"
 #include "postmaster/walwriter.h"
 #include "replication/logicallauncher.h"
+#include "replication/reorderbuffer.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/walreceiver.h"
+#include "replication/walsender.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2021,6 +2024,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_SIGHUP, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 998080e0e4..eaafdb3035 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -355,6 +355,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = on			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d906734750..6a8192ad83 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 214dc6c29e..75b4b2040d 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -17,6 +17,7 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"
 
 extern ObjectAddress CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										bool isTopLevel);
@@ -28,4 +29,7 @@ extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
 extern char defGetStreamingMode(DefElem *def);
 
+extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn,
+										char *slotname, bool missing_ok);
+
 #endif							/* SUBSCRIPTIONCMDS_H */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index bef8a7162e..085d729620 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -33,7 +33,6 @@ typedef struct IdentifySystemCmd
 	NodeTag		type;
 } IdentifySystemCmd;
 
-
 /* ----------------------
  *		BASE_BACKUP command
  * ----------------------
diff --git a/src/include/replication/logicallauncher.h b/src/include/replication/logicallauncher.h
index a07c9cb311..02499a7e66 100644
--- a/src/include/replication/logicallauncher.h
+++ b/src/include/replication/logicallauncher.h
@@ -15,9 +15,11 @@
 extern PGDLLIMPORT int max_logical_replication_workers;
 extern PGDLLIMPORT int max_sync_workers_per_subscription;
 extern PGDLLIMPORT int max_parallel_apply_workers_per_subscription;
+extern PGDLLIMPORT bool enable_syncslot;
+
 
 extern void ApplyLauncherRegister(void);
-extern void ApplyLauncherMain(Datum main_arg);
+extern void LauncherMain(Datum main_arg);
 
 extern Size ApplyLauncherShmemSize(void);
 extern void ApplyLauncherShmemInit(void);
@@ -31,4 +33,9 @@ extern bool IsLogicalLauncher(void);
 
 extern pid_t GetLeaderApplyWorkerPid(pid_t pid);
 
+extern void ShutDownSlotSync(void);
+
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+
 #endif							/* LOGICALLAUNCHER_H */
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index bbd71d0b42..baad5a8f3a 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -19,6 +19,7 @@ extern PGDLLIMPORT volatile sig_atomic_t ParallelApplyMessagePending;
 extern void ApplyWorkerMain(Datum main_arg);
 extern void ParallelApplyWorkerMain(Datum main_arg);
 extern void TablesyncWorkerMain(Datum main_arg);
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
 
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index ca06e5b1ad..6c300da45a 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -15,7 +15,6 @@
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "storage/spin.h"
-#include "replication/walreceiver.h"
 
 /*
  * Behaviour of replication slots, upon release or crash.
@@ -52,6 +51,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +119,13 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Is this a slot created by a sync-slot worker?
+	 *
+	 * Relevant for logical slots on the physical standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)?
 	 * Only relevant for logical slots on the primary server.
@@ -227,7 +241,7 @@ extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
 								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
@@ -253,7 +267,6 @@ extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_l
 extern int	ReplicationSlotIndex(ReplicationSlot *slot);
 extern bool ReplicationSlotName(int index, Name name);
 extern void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot);
-extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok);
 
 extern void StartupReplicationSlots(void);
 extern void CheckPointReplicationSlots(bool is_shutdown);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 61bc8de72c..2f7ae9dc69 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -20,6 +20,7 @@
 #include "pgtime.h"
 #include "port/atomics.h"
 #include "replication/logicalproto.h"
+#include "replication/slot.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/latch.h"
@@ -280,6 +281,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -404,6 +420,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -429,6 +446,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 4378690ab0..435afb1ee1 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -1,7 +1,8 @@
 /*-------------------------------------------------------------------------
  *
  * worker_internal.h
- *	  Internal headers shared by logical replication workers.
+ *	  Internal headers shared by logical replication workers
+ *	  and slotsync workers.
  *
  * Portions Copyright (c) 2016-2023, PostgreSQL Global Development Group
  *
@@ -36,14 +37,9 @@ typedef enum LogicalRepWorkerType
 	WORKERTYPE_PARALLEL_APPLY,
 } LogicalRepWorkerType;
 
-typedef struct LogicalRepWorker
+/* Common data for Slotsync and LogicalRep workers */
+typedef struct LogicalWorkerHeader
 {
-	/* What type of worker is this? */
-	LogicalRepWorkerType type;
-
-	/* Time at which this worker was launched. */
-	TimestampTz launch_time;
-
 	/* Indicates if this slot is used or free. */
 	bool		in_use;
 
@@ -53,6 +49,19 @@ typedef struct LogicalRepWorker
 	/* Pointer to proc array. NULL if not running. */
 	PGPROC	   *proc;
 
+} LogicalWorkerHeader;
+
+/* Shared memory structure for logical replication workers. */
+typedef struct LogicalRepWorker
+{
+	LogicalWorkerHeader hdr;
+
+	/* Time at which this worker was launched. */
+	TimestampTz launch_time;
+
+	/* What type of worker is this? */
+	LogicalRepWorkerType type;
+
 	/* Database id to connect to. */
 	Oid			dbid;
 
@@ -96,6 +105,32 @@ typedef struct LogicalRepWorker
 	TimestampTz reply_time;
 } LogicalRepWorker;
 
+/*
+ * Shared memory structure for Slot-Sync worker. It is allocated by logical
+ * replication launcher and then read by each slot sync worker.
+ *
+ * It is protected by LWLock (SlotSyncWorkerLock). Each slot sync worker
+ * reading the structure needs to hold the lock in shared mode, whereas
+ * the logical replication launcher which updates it needs to hold the lock
+ * in exclusive mode.
+ */
+typedef struct SlotSyncWorkerInfo
+{
+	/*
+	 * The header used by slot-sync worker and logical rep worker.
+	 *
+	 * The header has 'generation'. For slot sync worker, it is not needed
+	 * but since slot sync worker uses some of the code infrastructure
+	 * provided by logical rep worker which needs 'generation', it is
+	 * thus retained for slot sync worker but is always kept as 0.
+	 */
+	LogicalWorkerHeader hdr;
+
+	/* The last sync-cycle time when the worker updated any of the slots. */
+	TimestampTz last_update_time;
+
+}			SlotSyncWorkerInfo;
+
 /*
  * State of the transaction in parallel apply worker.
  *
@@ -234,12 +269,16 @@ extern PGDLLIMPORT struct WalReceiverConn *LogRepWorkerWalRcvConn;
 /* Worker and subscription objects. */
 extern PGDLLIMPORT Subscription *MySubscription;
 extern PGDLLIMPORT LogicalRepWorker *MyLogicalRepWorker;
+extern PGDLLIMPORT SlotSyncWorkerInfo *SlotSyncWorker;
 
 extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
 extern void logicalrep_worker_attach(int slot);
+extern void slotsync_worker_attach(void);
+extern void slotsync_worker_detach(int code, Datum arg);
+extern void slotsync_drop_initiated_slots(void);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
 extern List *logicalrep_workers_find(Oid subid, bool only_running);
@@ -328,9 +367,9 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
-#define isParallelApplyWorker(worker) ((worker)->in_use && \
+#define isParallelApplyWorker(worker) ((worker)->hdr.in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
-#define isTablesyncWorker(worker) ((worker)->in_use && \
+#define isTablesyncWorker(worker) ((worker)->hdr.in_use && \
 								   (worker)->type == WORKERTYPE_TABLESYNC)
 
 static inline bool
@@ -342,14 +381,14 @@ am_tablesync_worker(void)
 static inline bool
 am_leader_apply_worker(void)
 {
-	Assert(MyLogicalRepWorker->in_use);
+	Assert(MyLogicalRepWorker->hdr.in_use);
 	return (MyLogicalRepWorker->type == WORKERTYPE_APPLY);
 }
 
 static inline bool
 am_parallel_apply_worker(void)
 {
-	Assert(MyLogicalRepWorker->in_use);
+	Assert(MyLogicalRepWorker->hdr.in_use);
 	return isParallelApplyWorker(MyLogicalRepWorker);
 }
 
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
index bff0f52a46..e7a00bde12 100644
--- a/src/test/recovery/t/050_verify_slot_order.pl
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -146,4 +146,131 @@ $result = $subscriber1->safe_psql('postgres',
 	"SELECT count(*) = $primary_row_count FROM tab_int;");
 is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
 
+# Test logical failover slots on the standby
+# Configure standby3 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub1_slot->| ----> subscriber1 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb3_slot--->| ----> standby3 (connected via streaming replication)
+#                                        |                 lsub1_slot(synced_slot)
+
+# Cleanup old standby_slot_names
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = ''
+));
+$primary->start;
+
+$primary->psql('postgres',
+		q{SELECT pg_create_physical_replication_slot('sb3_slot');});
+
+$backup_name = 'backup2';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+my $connstr_1 = $primary->connstr;
+$standby3->stop;
+$standby3->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb3_slot'
+});
+$standby3->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+$standby3->start;
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb3_slot'
+));
+$primary->start;
+
+# Restart the standby
+$standby3->restart;
+
+# Wait for the standby to start sync
+my $offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots;}
+  ),
+  'lsub1_slot',
+  'failover slot was created');
+
+# Verify slot properties on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|r",
+  'logical slot has sync_state as ready and failover as true on standby');
+
+# Verify slot properties on the primary
+is( $primary->safe_psql('postgres',
+    q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|n",
+  'logical slot has sync_state as none and failover as true on primary');
+
+# Test to confirm that restart_lsn of the logical slot on the primary is synced to the standby
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_int;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# let the slots get synced on the standby
+sleep 2;
+
+# Get the restart_lsn for the logical slot lsub1_slot on the primary
+my $primary_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that restart_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'restart_lsn of slot lsub1_slot synced to standby');
+
+# Get the confirmed_flush_lsn for the logical slot lsub1_slot on the primary
+$primary_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that confirmed_flush_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub1_slot synced to the standby');
+
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d80d30e99c..d37eeb8a47 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1432,6 +1432,7 @@ LimitState
 LimitStateCond
 List
 ListCell
+ListDBForLogicalSlotsCmd
 ListDictionary
 ListParsedLex
 ListenAction
@@ -1511,6 +1512,7 @@ LogicalSlotInfo
 LogicalSlotInfoArr
 LogicalTape
 LogicalTapeSet
+LogicalWorkerHeader
 LsnReadQueue
 LsnReadQueueNextFun
 LsnReadQueueNextStatus
@@ -2314,6 +2316,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2572,6 +2575,8 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorker
+SlotSyncWorkerInfo
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.30.0.windows.2



  [application/octet-stream] v39_2-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (134.3K, ../../OS0PR01MB57161938A348977127D56A6694BDA@OS0PR01MB5716.jpnprd01.prod.outlook.com/4-v39_2-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From 24dba0abff51672a3505e27adf36b395d7412f4d Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Tue, 21 Nov 2023 14:50:21 +0530
Subject: [PATCH v39 1/3] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to the
physical standbys so that logical replication can be resumed after failover. It
is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API. Examples:

CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

(failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of pg_replication_slots
view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).

A new walreceiver API walrcv_alter_slot has been introduced to
enable the failover of the slot on publisher node.
---
 contrib/test_decoding/expected/slot.out       |  58 ++++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |   9 +-
 doc/src/sgml/ref/create_subscription.sgml     |  23 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       |  91 ++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  44 ++-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  51 ++-
 src/backend/replication/logical/worker.c      |  72 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 182 +++++++++-
 src/backend/replication/slotfuncs.c           |  19 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 327 +++++++++++++++++-
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_upgrade/info.c                     |   6 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 src/test/recovery/t/050_verify_slot_order.pl  | 149 ++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 +++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 48 files changed, 1334 insertions(+), 163 deletions(-)
 create mode 100644 src/test/recovery/t/050_verify_slot_order.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 93735e3aea..6440378d5c 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4344,6 +4344,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..056404af5d 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..fa6cd1c43f 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,29 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..c3187e7b9a 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,29 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If only the slot_name is specified (without create_slot option),
+			 * it is possible that the user intends to use an existing slot on
+			 * the publisher, so here we enable failover for the slot if
+			 * requested.
+			 */
+			else if (opts.slot_name && failover_enabled)
+			{
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("enabled failover for replication slot \"%s\" on publisher",
+								opts.slot_name)));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,8 +1326,8 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
@@ -1288,6 +1335,12 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when failover is enabled"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
 
 					/* Make sure refresh sees the new list of publications. */
@@ -1334,8 +1387,8 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
@@ -1347,6 +1400,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
 
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when failover is enabled"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
 
 					/* Refresh the new list of publications. */
@@ -1392,6 +1455,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when failover is enabled"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
 
 				AlterSubscription_refresh(sub, opts.copy_data, NULL);
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 60d5c1fc40..336c2bec99 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -883,8 +887,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -913,7 +917,14 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+		{
+			appendStringInfoString(&cmd, "FAILOVER");
+			if (use_new_options_syntax)
+				appendStringInfoString(&cmd, ", ");
+			else
+				appendStringInfoChar(&cmd, ' ');
+		}
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -982,6 +993,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					 bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index df3c42eb5d..1eaee4197b 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -614,15 +614,26 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
+									MySubscription->name)));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that failover can be enabled",
+									MySubscription->name)));
+
 				should_exit = true;
 			}
 		}
@@ -1420,7 +1431,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1722,10 +1734,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1733,9 +1747,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1749,9 +1762,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..0b7d632922 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,9 +132,41 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * the failover = true when creating the subscription. Enabling failover allows
+ * us to smoothly transition to the standby in case the primary gets promoted,
+ * ensuring that we can subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby. During syncing, the local restart_lsn and/or local catalog_xmin of
+ * the newly created slot on the standby are typically ahead of those on the
+ * primary. Therefore, the standby needs to wait for the primary server's
+ * restart_lsn and catalog_xmin to catch up, which takes time.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
+
 #include "postgres.h"
 
 #include <sys/stat.h>
@@ -3947,6 +3979,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4515,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4573,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4613,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..c96a1700cd 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List	   *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -251,7 +263,8 @@ ReplicationSlotValidateName(const char *name, int elevel)
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +324,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +693,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2198,140 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+
+	/* Need a modifiable copy of string */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into list of identifiers */
+	if (!SplitIdentifierString(rawname, ',', &elemlist))
+	{
+		/* syntax error in name list */
+		GUC_check_errdetail("List syntax is invalid.");
+		goto ret_standby_slot_names_ng;
+	}
+
+	/*
+	 * Verify 'type' of slot now.
+	 *
+	 * Skip check if replication slots' data is not initialized yet i.e. we
+	 * are in startup process.
+	 */
+	if (!ReplicationSlotCtl)
+		goto ret_standby_slot_names_ok;
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+			goto ret_standby_slot_names_ng;
+
+		if (SlotIsLogical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			goto ret_standby_slot_names_ng;
+		}
+	}
+
+ret_standby_slot_names_ok:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return true;
+
+ret_standby_slot_names_ng:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return false;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..fb6e37d2c3 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -679,6 +689,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +745,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +785,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 2398167f49..e27d231174 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -386,7 +386,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..3e44228bde 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool	failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,233 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up logical walsenders with failover-enabled slots if the physical slot
+ * of the current walsender is specified in standby_slot_names GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
+		bool		invalidated = false;
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (slot && SlotIsPhysical(slot))
+		{
+			SpinLockAcquire(&slot->mutex);
+			restart_lsn = slot->data.restart_lsn;
+			invalidated = slot->data.invalidated != RS_INVAL_NONE;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Continue if the current slot hasn't caught up. */
+		if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
+			restart_lsn < wait_for_lsn)
+		{
+			/* Log warning if no active_pid for this physical slot */
+			if (slot->active_pid == 0)
+				ereport(WARNING,
+						errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+							   name, "standby_slot_names"),
+						errdetail("Logical replication is waiting on the "
+								  "standby associated with \"%s\"", name),
+						errhint("Consider starting standby associated with "
+								"\"%s\" or amend standby_slot_names", name));
+
+			continue;
+		}
+
+		/*
+		 * It may happen that the slot specified in standby_slot_names GUC
+		 * value is dropped, so let's skip over it.
+		 */
+		else if (!slot)
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+
+		/*
+		 * If logical slot name is given in standby_slot_names, give WARNING
+		 * and skip it. Since it is harmless, so WARNING should be enough, no
+		 * need to error-out.
+		 */
+		else if (SlotIsLogical(slot))
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+
+		/*
+		 * Specified physical slot may have been invalidated, so no point in
+		 * waiting for it.
+		 */
+		else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
+			warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+		else
+			Assert(restart_lsn >= wait_for_lsn);
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, the
+ * function also waits for all the specified streaming replication standby
+ * servers to confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr if we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
 	if (RecentFlushPtr != InvalidXLogRecPtr &&
 		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know we
+		 * have enough WAL available and all the standby servers have confirmed
+		 * receipt of WAL up to RecentFlushPtr. This is particularly interesting
+		 * if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1825,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1840,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,8 +1879,14 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
 			break;
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
@@ -1654,9 +1927,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2094,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2331,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3594,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3665,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another CV
+	 * that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b764ef6998..e5e7bb23f9 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4562,6 +4562,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e48c066a5b..998080e0e4 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -326,6 +326,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..a348932e62 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,14 +679,17 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
+
 		slotinfos = (LogicalSlotInfo *) pg_malloc(sizeof(LogicalSlotInfo) * num_slots);
 
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +700,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..f6ac78418e 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced
+								 * to the physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 5b01cf8c40..0a1c467ed0 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -158,7 +158,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -172,8 +172,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 006e10f5d2..46e875c48b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3306,7 +3306,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb58dee3bc..d906734750 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..ca06e5b1ad 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)?
+	 * Only relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 04b439dc50..61bc8de72c 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -356,9 +356,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									   const char *slotname,
+									   bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -400,6 +411,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -429,8 +441,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 268f8e8d0f..1fcc22a127 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -14,6 +14,8 @@
 
 #include <signal.h>
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -47,6 +49,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 47854b5cd4..4378690ab0 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -258,7 +258,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..3be3ee52fc 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_verify_slot_order.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
new file mode 100644
index 0000000000..bff0f52a46
--- /dev/null
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -0,0 +1,149 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres', "CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+$subscriber1->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true
+$subscriber1->safe_psql('postgres',
+		"CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);");
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+$subscriber2->safe_psql('postgres',
+		"CREATE SUBSCRIPTION mysub2 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);");
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby comes
+# up.
+$standby1->stop;
+
+# Create some data on primary
+my $primary_row_count = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = 0 FROM tab_int;");
+is($result, 't', "subscriber1 doesn't get data from primary until standby1 acknowledges changes");
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 86a9886d4f..d80d30e99c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3862,6 +3863,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.30.0.windows.2



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-11-27 10:38           ` Amit Kapila <[email protected]>
  2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2 siblings, 1 reply; 113+ messages in thread

From: Amit Kapila @ 2023-11-27 10:38 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; +Cc: shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Mon, Nov 27, 2023 at 2:27 PM Zhijie Hou (Fujitsu)
<[email protected]> wrote:
>
> Here is the updated version(v39_2) which include all the changes made in 0002.
> Please use for review, and sorry for the confusion.
>

--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -8,20 +8,27 @@
  *   src/backend/replication/logical/launcher.c
  *
  * NOTES
- *   This module contains the logical replication worker launcher which
- *   uses the background worker infrastructure to start the logical
- *   replication workers for every enabled subscription.
+ *   This module contains the replication worker launcher which
+ *   uses the background worker infrastructure to:
+ *   a) start the logical replication workers for every enabled subscription
+ *      when not in standby_mode.
+ *   b) start the slot sync worker for logical failover slots synchronization
+ *      from the primary server when in standby_mode.

I was wondering do we really need a launcher on standby to invoke
sync-slot worker. If so, why? I guess it may be required for previous
versions where we were managing work for multiple slot-sync workers
which is also questionable in the sense of whether launcher is the
right candidate for the same but now with the single slot-sync worker,
it doesn't seem worth having it. What do you think?

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-11-28 03:13             ` shveta malik <[email protected]>
  2023-11-28 06:49               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-11-28 03:13 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; +Cc: Zhijie Hou (Fujitsu) <[email protected]>; Drouvot, Bertrand <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Mon, Nov 27, 2023 at 4:08 PM Amit Kapila <[email protected]> wrote:
>
> On Mon, Nov 27, 2023 at 2:27 PM Zhijie Hou (Fujitsu)
> <[email protected]> wrote:
> >
> > Here is the updated version(v39_2) which include all the changes made in 0002.
> > Please use for review, and sorry for the confusion.
> >
>
> --- a/src/backend/replication/logical/launcher.c
> +++ b/src/backend/replication/logical/launcher.c
> @@ -8,20 +8,27 @@
>   *   src/backend/replication/logical/launcher.c
>   *
>   * NOTES
> - *   This module contains the logical replication worker launcher which
> - *   uses the background worker infrastructure to start the logical
> - *   replication workers for every enabled subscription.
> + *   This module contains the replication worker launcher which
> + *   uses the background worker infrastructure to:
> + *   a) start the logical replication workers for every enabled subscription
> + *      when not in standby_mode.
> + *   b) start the slot sync worker for logical failover slots synchronization
> + *      from the primary server when in standby_mode.
>
> I was wondering do we really need a launcher on standby to invoke
> sync-slot worker. If so, why? I guess it may be required for previous
> versions where we were managing work for multiple slot-sync workers
> which is also questionable in the sense of whether launcher is the
> right candidate for the same but now with the single slot-sync worker,
> it doesn't seem worth having it. What do you think?
>
> --

Yes, earlier a manager process was needed to manage multiple slot-sync
workers and distribute load among them, but now that does not seem
necessary. I gave it a try (PoC) and it seems to work well.  If  there
are no objections to this approach, I can share the patch soon.

thanks
Shveta






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-11-28 06:49               ` Drouvot, Bertrand <[email protected]>
  2023-11-28 09:40                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-11-28 06:49 UTC (permalink / raw)
  To: shveta malik <[email protected]>; Amit Kapila <[email protected]>; +Cc: Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 11/28/23 4:13 AM, shveta malik wrote:
> On Mon, Nov 27, 2023 at 4:08 PM Amit Kapila <[email protected]> wrote:
>>
>> On Mon, Nov 27, 2023 at 2:27 PM Zhijie Hou (Fujitsu)
>> <[email protected]> wrote:
>>>
>>> Here is the updated version(v39_2) which include all the changes made in 0002.
>>> Please use for review, and sorry for the confusion.
>>>
>>
>> --- a/src/backend/replication/logical/launcher.c
>> +++ b/src/backend/replication/logical/launcher.c
>> @@ -8,20 +8,27 @@
>>    *   src/backend/replication/logical/launcher.c
>>    *
>>    * NOTES
>> - *   This module contains the logical replication worker launcher which
>> - *   uses the background worker infrastructure to start the logical
>> - *   replication workers for every enabled subscription.
>> + *   This module contains the replication worker launcher which
>> + *   uses the background worker infrastructure to:
>> + *   a) start the logical replication workers for every enabled subscription
>> + *      when not in standby_mode.
>> + *   b) start the slot sync worker for logical failover slots synchronization
>> + *      from the primary server when in standby_mode.
>>
>> I was wondering do we really need a launcher on standby to invoke
>> sync-slot worker. If so, why? I guess it may be required for previous
>> versions where we were managing work for multiple slot-sync workers
>> which is also questionable in the sense of whether launcher is the
>> right candidate for the same but now with the single slot-sync worker,
>> it doesn't seem worth having it. What do you think?
>>
>> --
> 
> Yes, earlier a manager process was needed to manage multiple slot-sync
> workers and distribute load among them, but now that does not seem
> necessary. I gave it a try (PoC) and it seems to work well.  If  there
> are no objections to this approach, I can share the patch soon.
> 

+1 on this new approach, thanks!

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 06:49               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-11-28 09:40                 ` shveta malik <[email protected]>
  2023-11-28 09:44                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 15:58                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  0 siblings, 2 replies; 113+ messages in thread

From: shveta malik @ 2023-11-28 09:40 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Tue, Nov 28, 2023 at 12:19 PM Drouvot, Bertrand
<[email protected]> wrote:
>
> Hi,
>
> On 11/28/23 4:13 AM, shveta malik wrote:
> > On Mon, Nov 27, 2023 at 4:08 PM Amit Kapila <[email protected]> wrote:
> >>
> >> On Mon, Nov 27, 2023 at 2:27 PM Zhijie Hou (Fujitsu)
> >> <[email protected]> wrote:
> >>>
> >>> Here is the updated version(v39_2) which include all the changes made in 0002.
> >>> Please use for review, and sorry for the confusion.
> >>>
> >>
> >> --- a/src/backend/replication/logical/launcher.c
> >> +++ b/src/backend/replication/logical/launcher.c
> >> @@ -8,20 +8,27 @@
> >>    *   src/backend/replication/logical/launcher.c
> >>    *
> >>    * NOTES
> >> - *   This module contains the logical replication worker launcher which
> >> - *   uses the background worker infrastructure to start the logical
> >> - *   replication workers for every enabled subscription.
> >> + *   This module contains the replication worker launcher which
> >> + *   uses the background worker infrastructure to:
> >> + *   a) start the logical replication workers for every enabled subscription
> >> + *      when not in standby_mode.
> >> + *   b) start the slot sync worker for logical failover slots synchronization
> >> + *      from the primary server when in standby_mode.
> >>
> >> I was wondering do we really need a launcher on standby to invoke
> >> sync-slot worker. If so, why? I guess it may be required for previous
> >> versions where we were managing work for multiple slot-sync workers
> >> which is also questionable in the sense of whether launcher is the
> >> right candidate for the same but now with the single slot-sync worker,
> >> it doesn't seem worth having it. What do you think?
> >>
> >> --
> >
> > Yes, earlier a manager process was needed to manage multiple slot-sync
> > workers and distribute load among them, but now that does not seem
> > necessary. I gave it a try (PoC) and it seems to work well.  If  there
> > are no objections to this approach, I can share the patch soon.
> >
>
> +1 on this new approach, thanks!

PFA v40. This patch has removed Logical Replication Launcher support
to launch slotsync worker.  The slot-sync worker is now registered as
bgworker with postmaster, with
bgw_start_time=BgWorkerStart_ConsistentState and
bgw_restart_time=60sec.

On removal of launcher, now all the validity checks have been shifted
to slot-sync worker itself.  This brings us to some point of concerns:

a) We still need to maintain  RecoveryInProgress() check in slotsync
worker. Since worker has the start time of
BgWorkerStart_ConsistentState, it will be started on non-standby as
well. So to ensure that it exists on non-standby, "RecoveryInProgress"
has been introduced at the beginning of the worker. But once it exits,
postmaster will not restart it since it will be clean-exist i.e.
proc_exit(0) (the restart logic of postmaster comes into play only
when there is an abnormal exit). But to exit for the first time on
non-standby, we need that Recovery related check in worker.

b) "enable_syncslot" check is moved to slotsync worker now. Since
enable_syncslot is PGC_SIGHUP, so proc_exit(1) is currently used to
exit the worker if 'enable_syncslot' is found to be disabled.
'proc_exit(1)' has been used in order to ensure that the worker is
restarted and GUCs are checked again after restart_time. Downside of
this approach is, if someone has kept "enable_syncslot" as disabled
permanently even on standby, slotsync worker will keep on restarting
and exiting.

So to overcome the above pain-points, I think a potential approach
will be to start slotsync worker only if 'enable_syncslot' is on and
the system is non-standby. Potential ways (each with some issues) are:

1) Use the current way i.e. register slot-sync worker as bgworker with
postmaster, but introduce extra checks in 'maybe_start_bgworkers'. But
this seems more like a hack. This will need extra changes as currently
once 'maybe_start_bgworkers' is attempted by postmaster, it will
attempt again to start any worker only if the worker had abnormal exit
and restart_time !=0. The current postmatser will not attempt to start
worker on any GUC change.

2) Another way maybe to treat slotsync worker as special case and
separate out the start/restart of slotsync worker from bgworker, and
follow what we do for autovacuum launcher(StartAutoVacLauncher) to
keep starting it in the postmaster loop(ServerLoop). In this way, we
may be able to add more checks before starting worker. But by opting
this approach, we will have to manage slotsync worker completely by
ourself as it will be no longer be part of existing
bgworker-registration infra. If this seems okay and there are no other
better options, it can be analyzed further in detail.

3) Another approach could be, in order to solve issue (a), introduce a
new start_time 'BgWorkerStart_ConsistentState_HotStandby' which means
start a bgworker only if consistent state is reached and the system is
standby. And for issue (b), lets retain check of enable_syncslot in
the worker itself but make it 'PGC_POSTMASTER'. This will ensure we
can safely exit the worker(proc_exit(0) if enable_syncslot is disabled
and postmaster will not restart it. But I'm not sure if making it
"PGC_POSTMASTER" is acceptable from the user's perspective.

thanks
Shveta


Attachments:

  [application/octet-stream] v40-0002-Add-logical-slot-sync-capability-to-the-physical.patch (75.3K, ../../CAJpy0uAa6HnPKCO8pM7xFfEmzXtXDmgRvX5ErJX8e8-mWyvXYQ@mail.gmail.com/2-v40-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From 62dc9750046fe6a8a3b29915a58ea138891873b7 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Mon, 27 Nov 2023 16:47:02 +0800
Subject: [PATCH v40 2/3] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover. All the failover logical
replication slots on the primary (assuming configurations are
appropriate) are automatically created on the physical standbys and
are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The nap time of worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. And if
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated. If a logical slot on the primary is valid but is invalidated
on the standby due to conflict (say required rows removed on the primary),
then that slot is dropped and recreated on the standby in next sync-cycle.
It is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently).

Slots synced on the standby can be identified using 'sync_state' column of
pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but waiting for the remote slot on the
     primary server to catch up.
'r': ready for periodic syncs.
---
 doc/src/sgml/config.sgml                      |   35 +-
 doc/src/sgml/logicaldecoding.sgml             |   13 +
 doc/src/sgml/system-views.sgml                |   23 +
 src/backend/access/transam/xlogrecovery.c     |   10 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    4 +
 src/backend/postmaster/postmaster.c           |    3 +
 .../libpqwalreceiver/libpqwalreceiver.c       |   44 +-
 src/backend/replication/logical/Makefile      |    1 +
 src/backend/replication/logical/logical.c     |   20 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1249 +++++++++++++++++
 src/backend/replication/logical/tablesync.c   |    1 +
 src/backend/replication/slot.c                |   18 +-
 src/backend/replication/slotfuncs.c           |   33 +-
 src/backend/replication/walsender.c           |    2 +-
 src/backend/storage/ipc/ipci.c                |    2 +
 src/backend/tcop/postgres.c                   |   12 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   10 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/commands/subscriptioncmds.h       |    4 +
 src/include/replication/slot.h                |   19 +-
 src/include/replication/walreceiver.h         |   19 +
 src/include/replication/worker_internal.h     |   12 +
 src/test/recovery/t/050_verify_slot_order.pl  |  127 ++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    2 +
 30 files changed, 1670 insertions(+), 18 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 6440378d5c..b2da901ad6 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4357,6 +4357,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must enable
+        <varname>enable_syncslot</varname> for the standbys to receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4550,10 +4556,15 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           A password needs to be provided too, if the sender demands password
           authentication.  It can be provided in the
           <varname>primary_conninfo</varname> string, or in a separate
-          <filename>~/.pgpass</filename> file on the standby server (use
+          <filename>~/.pgpass</filename> file on the standby server. (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          Specify <literal>dbname</literal> in
+          <varname>primary_conninfo</varname> string to allow synchronization
+          of slots from the primary server to the standby server.
+          This will only be used for slot synchronization. It is ignored
+          for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4878,6 +4889,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..90bec06f36 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,19 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and set
+     <varname>enable_syncslot</varname> on the standby. The physical replication
+     slot for the standby should be listed in
+     <varname>standby_slot_names</varname> on the primary to prevent the
+     subscriber from consuming changes faster than the hot standby.
+     Additionally, similar to creating a logical replication slot on the hot
+     standby, <varname>hot_standby_feedback</varname> should be set on the
+     standby and a physical slot between the primary and the standby should be
+     used.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..f08beb83e1 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,29 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has enabled slots synchronization.
+      </para>
+      <para>
+       State code:
+       <literal>n</literal> = none for user created slots,
+       <literal>i</literal> = sync initiated for the slot but slot is not ready
+        yet for periodic syncs,
+       <literal>r</literal> = ready for periodic syncs
+      </para>
+      <para>
+      The primary server will have sync_state as 'n' for all the slots.
+      But if the standby is promoted to become the new primary server,
+      sync_state can be seen 'r' as well. On this new primary server, slots
+      with sync_state as 'r' and 'n' behaves the same.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c61566666a..9fb09ba0fc 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1436,15 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 48a9924527..c65a678c95 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -22,6 +22,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "replication/worker_internal.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -130,6 +131,9 @@ static const struct
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 7a5cd06c5c..f1794be81e 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -117,6 +117,7 @@
 #include "postmaster/syslogger.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -1001,6 +1002,8 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	SlotSyncWorkerRegister();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 336c2bec99..5f82d01e6d 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -413,6 +416,45 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	PQconninfoOption *opt;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
@@ -998,7 +1040,7 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
  */
 static void
 libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
-					 bool failover)
+					bool failover)
 {
 	StringInfoData cmd;
 	PGresult   *res;
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..7de68a61e2 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,26 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	/*
+	 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+	 * promotion.
+	 */
+	if (!RecoveryInProgress() && slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+		elog(ERROR, "replication slot \"%s\" was not synced completely from the primary server",
+			 NameStr(slot->data.name));
+
+	/*
+	 * Do not allow consumption of a "synchronized" slot until the standby
+	 * gets promoted.
+	 */
+	if (RecoveryInProgress() && slot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot use replication slot \"%s\" for logical decoding",
+						NameStr(slot->data.name)),
+				 errdetail("This slot is being synced from the primary server."),
+				 errhint("Specify another replication slot.")));
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..3b3facdcc2
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1249 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * It also takes care of dropping the slots which were created by it and are
+ * currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/table.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_database.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+typedef struct SlotSyncWorkerCtx
+{
+	pid_t		pid;
+	slock_t		mutex;
+} SlotSyncWorkerCtx;
+
+SlotSyncWorkerCtx *SlotSyncWorker = NULL;
+
+/* GUC variable */
+bool		enable_syncslot = false;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+/*
+ * Number of attempts for wait_for_primary_slot_catchup() after
+ * which it aborts the wait and the slot sync worker then moves
+ * to the next slot creation/sync.
+ */
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
+
+/* The last sync-cycle time when the worker updated any of the slots. */
+TimestampTz last_update_time;
+
+static void ProcessSlotSyncInterrupts(WalReceiverConn **wrconn);
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * *persist will be set to false if the slot has disappeared or was invalidated
+ * on the primary; otherwise, it will be set to true.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *persist)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+	if (persist)
+		*persist = true;
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn, confirmed_flush_lsn,"
+					 " catalog_xmin FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		XLogRecPtr	new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *slot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Handle any termination request if any */
+		ProcessSlotSyncInterrupts(&wrconn);
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot info for slot \"%s\" from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" disappeared from the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(slot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" invalidated on the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			ExecClearTuple(slot);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * Once we got valid restart_lsn, then confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(slot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\" and moving"
+						   " to the next slot, will attempt creating it again",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up.
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			slots = lappend(slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(slots);
+}
+
+/*
+ * Get list of local logical slot names which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slot_names(void)
+{
+	List	   *localSyncedSlots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			localSyncedSlots = lappend(localSyncedSlots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return localSyncedSlots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+check_sync_slot_validity(ReplicationSlot *local_slot, List *remote_slots,
+						 bool *locally_invalidated)
+{
+	ListCell   *cell;
+
+	foreach(cell, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Also drop the slots that are valid on the primary that got invalidated
+ * on the standby due to conflict (say required rows removed on the primary).
+ * The assumption is, that these will get recreated in next sync-cycle and
+ * it is okay to drop and recreate such slots as long as these are not
+ * consumable on the standby (which is the case currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slot_list = NIL;
+	ListCell   *lc_slot;
+
+	/*
+	 * Get the list of local 'synced' slot so that those not on remote could
+	 * be dropped.
+	 */
+	local_slot_list = get_local_synced_slot_names();
+
+	foreach(lc_slot, local_slot_list)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc_slot);
+		bool		local_exists = false;
+		bool		locally_invalidated = false;
+
+		local_exists = check_sync_slot_validity(local_slot, remote_slot_list,
+												&locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!local_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Constructs the query in order to get failover logical slots
+ * information from the primary server.
+ */
+static void
+construct_slot_query(StringInfo s)
+{
+	/*
+	 * Fetch slots with failover enabled.
+	 *
+	 * If we are on cascading standby, we should fetch only those slots from
+	 * the first standby which have sync_state as either 'n' or 'r'. Slots
+	 * with sync_state as 'i' are not sync ready yet. And when we are on the
+	 * first standby, the primary server is supposed to have slots with
+	 * sync_state as 'n' only (or it may have all 'n', 'r' and 'i' if standby
+	 * is promoted as primary). Thus in all the cases, filter sync_state !='i'
+	 * is appropriate one.
+	 */
+	appendStringInfo(s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover and sync_state != 'i'");
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ */
+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+					 bool *slot_updated)
+{
+	ReplicationSlot *s;
+	char		sync_state = 0;
+
+	/*
+	 * Make sure that concerned WAL is received before syncing slot to target
+	 * lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+
+		return;
+	}
+
+	/* Search for the named slot */
+	if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		SpinLockAcquire(&s->mutex);
+		sync_state = s->data.sync_state;
+		SpinLockRelease(&s->mutex);
+	}
+
+	StartTransactionCommand();
+
+	/*
+	 * Already existing slot (created by slot sync worker) and ready for sync,
+	 * acquire and sync it.
+	 */
+	if (sync_state == SYNCSLOT_STATE_READY)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&MyReplicationSlot->mutex);
+			MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&MyReplicationSlot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * With hot_standby_feedback enabled and invalidations handled
+		 * apropriately as above, this should never happen.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn)
+		{
+			ereport(ERROR,
+					errmsg("not synchronizing local slot \"%s\" LSN(%X/%X)"
+						   " to remote slot's LSN(%X/%X) as synchronization "
+						   " would move it backwards", remote_slot->name,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   LSN_FORMAT_ARGS(remote_slot->restart_lsn)));
+
+			goto cleanup;
+		}
+
+		if (remote_slot->confirmed_lsn != MyReplicationSlot->data.confirmed_flush ||
+			remote_slot->restart_lsn != MyReplicationSlot->data.restart_lsn ||
+			remote_slot->catalog_xmin != MyReplicationSlot->data.catalog_xmin)
+		{
+			/* Update LSN of slot to remote slot's current position */
+			local_slot_update(remote_slot);
+			ReplicationSlotSave();
+			*slot_updated = true;
+		}
+	}
+
+	/*
+	 * Already existing slot but not ready (i.e. waiting for the primary
+	 * server to catch-up), lets attempt to make it sync-ready now.
+	 */
+	else if (sync_state == SYNCSLOT_STATE_INITIATED)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * Refer the slot creation part (last 'else' block) for more details
+		 * on this wait.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+			{
+				goto cleanup;
+			}
+		}
+
+		/*
+		 * Wait for primary is over, update the lsns and mark the slot as
+		 * READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Save the changes */
+		ReplicationSlotMarkDirty();
+		ReplicationSlotSave();
+
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready now",
+							remote_slot->name));
+	}
+	/* User created slot with the same name exists, raise ERROR. */
+	else if (sync_state == SYNCSLOT_STATE_NONE)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("skipping sync of slot \"%s\" as it is a user created"
+						" slot", remote_slot->name),
+				 errdetail("This slot has failover enabled on the primary and"
+						   " thus is sync candidate but user created slot with"
+						   " the same name already exists on the standby")));
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+		ReplicationSlot *slot;
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover);
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * If the local restart_lsn and/or local catalog_xmin is ahead of
+		 * those on the remote then we cannot create the local slot in sync
+		 * with the primary server because that would mean moving the local
+		 * slot backwards and we might not have WALs retained for old LSN. In
+		 * this case we will wait for the primary server's restart_lsn and
+		 * catalog_xmin to catch up with the local one before attempting the
+		 * sync.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			bool		persist;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &persist))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid) and attempt the wait and synchronization in
+				 * the next cycle.
+				 */
+				if (persist)
+				{
+					/* Mark it as sync initiated */
+					SpinLockAcquire(&MyReplicationSlot->mutex);
+					slot->data.sync_state = SYNCSLOT_STATE_INITIATED;
+					SpinLockRelease(&MyReplicationSlot->mutex);
+
+					ReplicationSlotPersist();
+					*slot_updated = true;
+				}
+
+				goto cleanup;
+			}
+		}
+
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready now",
+							remote_slot->name));
+	}
+
+cleanup:
+
+	ReplicationSlotRelease();
+	CommitTransactionCommand();
+
+	return;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and update
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns nap time for the next sync-cycle.
+ */
+static long
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *slot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	MemoryContext oldctx = CurrentMemoryContext;
+	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	ListCell   *cell;
+	bool		slot_updated = false;
+	TimestampTz now;
+
+	/* The primary_slot_name is not set yet or WALs not received yet */
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+		return naptime;
+
+	/* The syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	/*
+	 * Make result tuples live outside TopTransactionContext to make them
+	 * accessible even after transaction is committed.
+	 */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct query to get slots info from the primary server */
+	initStringInfo(&s);
+	construct_slot_query(&s);
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(slot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+																	   5, &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(slot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(slot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(slot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(slot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(slot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(cell, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		synchronize_one_slot(wrconn, remote_slot, &slot_updated);
+	}
+
+	now = GetCurrentTimestamp();
+
+	/*
+	 * If any of the slots get updated in this sync-cycle, retain default
+	 * naptime and update 'last_update_time' in slot sync worker. But if no
+	 * activity is observed in this sync-cycle, then increase naptime provided
+	 * inactivity time reaches threshold.
+	 */
+	if (slot_updated)
+		last_update_time = now;
+
+	else if (TimestampDifferenceExceeds(last_update_time,
+										now, WORKER_INACTIVITY_THRESHOLD_MS))
+		naptime = WORKER_INACTIVITY_NAPTIME_MS;
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	return naptime;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker", &err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Checks if GUCs are set appropriately before starting slot sync worker
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+	/*
+	 * The sync feature itself is disabled, exit.
+	 *
+	 * TODO: Shall this check be moved to postmaster i.e. do not start the
+	 * worker if enable_syncslot is off?
+	 */
+	if (!enable_syncslot)
+	{
+		ereport(LOG,
+				errmsg("exiting slot sync worker as enable_syncslot is disabled."));
+
+		/*
+		 * Use exit status 1 so that it is restarted and enable_syncslot is
+		 * checked again
+		 */
+		proc_exit(1);
+	}
+
+	/*
+	 * Since the above GUC is set, check that other GUC settings
+	 * (primary_slot_name, hot_standby_feedback, wal_level, primary_conninfo)
+	 * are compatible with slot synchronization. If not, raise ERROR.
+	 */
+
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+		ereport(ERROR,
+				errmsg("skipping slots synchronization as primary_slot_name is not set"));
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+		ereport(ERROR,
+				errmsg("skipping slots synchronization as hot_standby_feedback is off"));
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+				errmsg("skipping slots synchronisation as it requires wal_level >= logical"));
+
+	/*
+	 * The primary_conninfo is required to make connection to primary for
+	 * getting slots information.
+	 */
+	if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+		ereport(ERROR,
+				errmsg("skipping slots synchronization as primary_conninfo is not set"));
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	*dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (*dbname == NULL)
+		ereport(ERROR,
+				errmsg("skipping slots synchronization as dbname is not specified in primary_conninfo"));
+
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs changed, validate the values again
+ * through validate_slotsync_parameters() which will exit the worker
+ * if validaity fails.
+ */
+static void
+slotsync_reread_config()
+{
+	char	   *conninfo = pstrdup(PrimaryConnInfo);
+	char	   *slotname = pstrdup(PrimarySlotName);
+	bool		syncslot = enable_syncslot;
+	bool		standbyfeedback = hot_standby_feedback;
+	bool		revalidate = false;
+	char	   *dbname;
+
+	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	Assert(dbname);
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if ((strcmp(conninfo, PrimaryConnInfo) != 0) ||
+		(strcmp(slotname, PrimarySlotName) != 0) ||
+		(syncslot != enable_syncslot) ||
+		(standbyfeedback != hot_standby_feedback))
+	{
+		revalidate = true;
+	}
+
+	pfree(conninfo);
+	pfree(slotname);
+
+	if (revalidate)
+	{
+		char	   *new_dbname;
+
+		validate_slotsync_parameters(&new_dbname);
+
+		/*
+		 * Since we have initialized this worker with old dbname, thus exit if
+		 * dbname changed. Let it get restarted and connect to new dbname
+		 * specified.
+		 */
+		if (strcmp(dbname, new_dbname) != 0)
+		{
+			ereport(ERROR,
+					errmsg("exiting slot sync woker as dbname in primary_conninfo changed"));
+		}
+	}
+}
+
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting"
+					   " down on receiving SIGINT"));
+
+		walrcv_disconnect(*wrconn);
+		proc_exit(0);
+	}
+
+
+	if (ConfigReloadPending)
+		slotsync_reread_config();
+}
+
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = 0;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	/*
+	 * This check is needed to exit slot-sync worker on the the primary
+	 * server.
+	 */
+	if (!RecoveryInProgress())
+	{
+		ereport(LOG, errmsg("exiting slot sync woker as it is not standby"));
+		proc_exit(0);
+	}
+
+	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	Assert(SlotSyncWorker->pid == 0);
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	validate_slotsync_parameters(&dbname);
+
+	/*
+	 * Connect to the database specified by user in PrimaryConnInfo. We need a
+	 * database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime;
+
+		ProcessSlotSyncInterrupts(&wrconn);
+
+		naptime = synchronize_slots(wrconn);
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsSlotSyncWorker(void)
+{
+	return SlotSyncWorker->pid == MyProcPid;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (!SlotSyncWorker->pid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Wait for it to die. */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long. */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (!SlotSyncWorker->pid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Allocate and initialize slow sync worker shared memory
+ */
+void
+SlotSyncWorkerShmemInit(void)
+{
+	Size		size;
+	bool		found;
+
+	size = sizeof(SlotSyncWorkerCtx);
+	size = MAXALIGN(size);
+
+	SlotSyncWorker = (SlotSyncWorkerCtx *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
+
+	if (!found)
+	{
+		memset(SlotSyncWorker, 0, size);
+		SpinLockInit(&SlotSyncWorker->mutex);
+	}
+}
+
+/*
+ * Register the background worker for slots synchronization.
+ */
+void
+SlotSyncWorkerRegister(void)
+{
+	BackgroundWorker bgw;
+
+	memset(&bgw, 0, sizeof(bgw));
+
+	/* We need database connection which needs shared-memory access as well. */
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+
+	/* Start as soon as a consistent state has been reached in a hot standby */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState;
+
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "replication slot sync worker");
+	snprintf(bgw.bgw_type, BGW_MAXLEN,
+			 "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
+	bgw.bgw_notify_pid = 0;
+	bgw.bgw_main_arg = (Datum) 0;
+
+	RegisterBackgroundWorker(&bgw);
+}
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 1eaee4197b..114f80971b 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "commands/subscriptioncmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index c96a1700cd..2c059dffca 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -325,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = SYNCSLOT_STATE_NONE;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -684,12 +686,26 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+	{
+		ReplicationSlotRelease();
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary.")));
+	}
+
 	ReplicationSlotDropAcquired();
 }
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index fb6e37d2c3..7b1e0c1552 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3e44228bde..edb3656cd2 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, false);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index a3d8eacb8d..44668d8efd 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -36,6 +36,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -293,6 +294,7 @@ CreateSharedMemoryAndSemaphores(void)
 	WalRcvShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index e415cf1f34..2bf47c494b 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -60,6 +60,7 @@
 #include "replication/logicalworker.h"
 #include "replication/slot.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
@@ -3286,6 +3287,17 @@ ProcessInterrupts(void)
 			 */
 			proc_exit(1);
 		}
+		else if (IsSlotSyncWorker())
+		{
+			ereport(DEBUG1,
+					(errmsg_internal("replication slot sync worker is shutting down due to administrator command")));
+
+			/*
+			 * Slot sync worker can be stopped at any time.
+			 * Use exit status 1 so the background worker is restarted.
+			 */
+			proc_exit(1);
+		}
 		else if (IsBackgroundWorker)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index e5e7bb23f9..dc0830349d 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2021,6 +2022,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_SIGHUP, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 998080e0e4..eaafdb3035 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -355,6 +355,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = on			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d906734750..6a8192ad83 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 214dc6c29e..75b4b2040d 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -17,6 +17,7 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"
 
 extern ObjectAddress CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										bool isTopLevel);
@@ -28,4 +29,7 @@ extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
 extern char defGetStreamingMode(DefElem *def);
 
+extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn,
+										char *slotname, bool missing_ok);
+
 #endif							/* SUBSCRIPTIONCMDS_H */
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index ca06e5b1ad..6c300da45a 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -15,7 +15,6 @@
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "storage/spin.h"
-#include "replication/walreceiver.h"
 
 /*
  * Behaviour of replication slots, upon release or crash.
@@ -52,6 +51,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +119,13 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Is this a slot created by a sync-slot worker?
+	 *
+	 * Relevant for logical slots on the physical standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)?
 	 * Only relevant for logical slots on the primary server.
@@ -227,7 +241,7 @@ extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
 								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
@@ -253,7 +267,6 @@ extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_l
 extern int	ReplicationSlotIndex(ReplicationSlot *slot);
 extern bool ReplicationSlotName(int index, Name name);
 extern void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot);
-extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok);
 
 extern void StartupReplicationSlots(void);
 extern void CheckPointReplicationSlots(bool is_shutdown);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 61bc8de72c..2f7ae9dc69 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -20,6 +20,7 @@
 #include "pgtime.h"
 #include "port/atomics.h"
 #include "replication/logicalproto.h"
+#include "replication/slot.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/latch.h"
@@ -280,6 +281,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -404,6 +420,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -429,6 +446,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 4378690ab0..3379b1ffbf 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -239,6 +239,11 @@ extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
+/* Slot sync worker objects */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+extern PGDLLIMPORT bool enable_syncslot;
+
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
@@ -328,6 +333,13 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
+extern void SlotSyncWorkerRegister(void);
+extern void ShutDownSlotSync(void);
+extern void slotsync_drop_initiated_slots(void);
+extern bool IsSlotSyncWorker(void);
+extern void SlotSyncWorkerShmemInit(void);
+
 #define isParallelApplyWorker(worker) ((worker)->in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
 #define isTablesyncWorker(worker) ((worker)->in_use && \
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
index bff0f52a46..e7a00bde12 100644
--- a/src/test/recovery/t/050_verify_slot_order.pl
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -146,4 +146,131 @@ $result = $subscriber1->safe_psql('postgres',
 	"SELECT count(*) = $primary_row_count FROM tab_int;");
 is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
 
+# Test logical failover slots on the standby
+# Configure standby3 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub1_slot->| ----> subscriber1 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb3_slot--->| ----> standby3 (connected via streaming replication)
+#                                        |                 lsub1_slot(synced_slot)
+
+# Cleanup old standby_slot_names
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = ''
+));
+$primary->start;
+
+$primary->psql('postgres',
+		q{SELECT pg_create_physical_replication_slot('sb3_slot');});
+
+$backup_name = 'backup2';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+my $connstr_1 = $primary->connstr;
+$standby3->stop;
+$standby3->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb3_slot'
+});
+$standby3->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+$standby3->start;
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb3_slot'
+));
+$primary->start;
+
+# Restart the standby
+$standby3->restart;
+
+# Wait for the standby to start sync
+my $offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots;}
+  ),
+  'lsub1_slot',
+  'failover slot was created');
+
+# Verify slot properties on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|r",
+  'logical slot has sync_state as ready and failover as true on standby');
+
+# Verify slot properties on the primary
+is( $primary->safe_psql('postgres',
+    q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|n",
+  'logical slot has sync_state as none and failover as true on primary');
+
+# Test to confirm that restart_lsn of the logical slot on the primary is synced to the standby
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_int;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# let the slots get synced on the standby
+sleep 2;
+
+# Get the restart_lsn for the logical slot lsub1_slot on the primary
+my $primary_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that restart_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'restart_lsn of slot lsub1_slot synced to standby');
+
+# Get the confirmed_flush_lsn for the logical slot lsub1_slot on the primary
+$primary_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that confirmed_flush_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub1_slot synced to the standby');
+
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d80d30e99c..fb84b9509a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2314,6 +2314,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2572,6 +2573,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorkerCtx
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.34.1



  [application/octet-stream] v40-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch (8.5K, ../../CAJpy0uAa6HnPKCO8pM7xFfEmzXtXDmgRvX5ErJX8e8-mWyvXYQ@mail.gmail.com/3-v40-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch)
  download | inline diff:
From 87b98876e33dd729156f26192724aca4790b1ccd Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Tue, 28 Nov 2023 10:52:08 +0530
Subject: [PATCH v40 3/3] Allow slot-sync worker to wait for the cascading
 standbys.

The GUC standby_slot_names is needed to be set on first standby
in order to allow it to wait for confirmation for cascading
standbys before updating logical 'synced' slots in slot-sync worker.
The intent is that the logical slots (synced ones) should not go
ahead of cascading standbys.

For the user created slots on first standby, we already have this wait
logic in place in logical walsender and in pg_logical_slot_get_changes_guts(),
but for synced slots (which can not be consumed yet), we need to make
sure that they are not going ahead of cascading standbys and that is
acheived by introducing the wait in slot-sync worker before we actually
update the slots.
---
 src/backend/replication/logical/slotsync.c | 89 ++++++++++++++++++++--
 src/backend/replication/walsender.c        | 11 +--
 src/include/replication/walsender.h        |  5 ++
 3 files changed, 90 insertions(+), 15 deletions(-)

diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 3b3facdcc2..49d97a38a6 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -101,7 +101,9 @@ bool		enable_syncslot = false;
 /* The last sync-cycle time when the worker updated any of the slots. */
 TimestampTz last_update_time;
 
-static void ProcessSlotSyncInterrupts(WalReceiverConn **wrconn);
+static void ProcessSlotSyncInterrupts(WalReceiverConn **wrconn,
+									  List **standby_slots);
+
 
 /*
  * Wait for remote slot to pass locally reserved position.
@@ -157,7 +159,7 @@ wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
 		CHECK_FOR_INTERRUPTS();
 
 		/* Handle any termination request if any */
-		ProcessSlotSyncInterrupts(&wrconn);
+		ProcessSlotSyncInterrupts(&wrconn, NULL /* standby_slots */ );
 
 		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
 
@@ -472,6 +474,52 @@ construct_slot_query(StringInfo s)
 					 " WHERE failover and sync_state != 'i'");
 }
 
+/*
+ * Wait for cascading physical standbys corresponding to physical slots
+ * specified in standby_slot_names GUC to confirm receiving given lsn.
+ */
+static void
+wait_for_standby_confirmation(XLogRecPtr wait_for_lsn,
+							  WalReceiverConn *wrconn)
+{
+	List	   *standby_slots;
+
+	/* Nothing to be done */
+	if (strcmp(standby_slot_names, "") == 0)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	for (;;)
+	{
+		int			rc;
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		/*
+		 * This will reload configuration and will refresh the standby_slots
+		 * as well provided standby_slot_names GUC is changed by the user.
+		 */
+		ProcessSlotSyncInterrupts(&wrconn, &standby_slots);
+
+		/*
+		 * XXX: Is waiting for 5 second before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   5000L,
+					   WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+}
+
 /*
  * Synchronize single slot to given position.
  *
@@ -743,6 +791,7 @@ synchronize_slots(WalReceiverConn *wrconn)
 	List	   *remote_slot_list = NIL;
 	MemoryContext oldctx = CurrentMemoryContext;
 	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	XLogRecPtr	max_confirmed_lsn = 0;
 	ListCell   *cell;
 	bool		slot_updated = false;
 	TimestampTz now;
@@ -795,6 +844,9 @@ synchronize_slots(WalReceiverConn *wrconn)
 		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
 		Assert(!isnull);
 
+		if (remote_slot->confirmed_lsn > max_confirmed_lsn)
+			max_confirmed_lsn = remote_slot->confirmed_lsn;
+
 		/*
 		 * It is possible to get null values for LSN and Xmin if slot is
 		 * invalidated on the primary server, so handle accordingly.
@@ -838,6 +890,17 @@ synchronize_slots(WalReceiverConn *wrconn)
 	 */
 	drop_obsolete_slots(remote_slot_list);
 
+	/*
+	 * If there are cascading standbys, wait for their confirmation before we
+	 * update synced logical slots locally.
+	 *
+	 * Instead of waiting on confirmation for lsn of each slot, let us wait
+	 * once for confirmation on max_confirmed_lsn. If that is confirmed by
+	 * each cascading standby, we are good to update all the slots.
+	 */
+	if (remote_slot_list)
+		wait_for_standby_confirmation(max_confirmed_lsn, wrconn);
+
 	/* Now sync the slots locally */
 	foreach(cell, remote_slot_list)
 	{
@@ -975,7 +1038,7 @@ validate_slotsync_parameters(char **dbname)
  * if validaity fails.
  */
 static void
-slotsync_reread_config()
+slotsync_reread_config(List **standby_slots)
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 	char	   *slotname = pstrdup(PrimarySlotName);
@@ -987,8 +1050,14 @@ slotsync_reread_config()
 	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
 	Assert(dbname);
 
-	ConfigReloadPending = false;
-	ProcessConfigFile(PGC_SIGHUP);
+	/*
+	 * Reload configs and recreate the standby_slot_names_list if GUC
+	 * standby_slot_names changed.
+	 */
+	if (standby_slots)
+		WalSndRereadConfigAndReInitSlotList(standby_slots);
+	else
+		ProcessConfigFile(PGC_SIGHUP);
 
 	if ((strcmp(conninfo, PrimaryConnInfo) != 0) ||
 		(strcmp(slotname, PrimarySlotName) != 0) ||
@@ -1025,7 +1094,8 @@ slotsync_reread_config()
  * Interrupt handler for main loop of slot sync worker.
  */
 static void
-ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
+ProcessSlotSyncInterrupts(WalReceiverConn **wrconn,
+						  List **standby_slots)
 {
 	CHECK_FOR_INTERRUPTS();
 
@@ -1041,7 +1111,10 @@ ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
 
 
 	if (ConfigReloadPending)
-		slotsync_reread_config();
+	{
+		ConfigReloadPending = false;
+		slotsync_reread_config(standby_slots);
+	}
 }
 
 /*
@@ -1120,7 +1193,7 @@ ReplSlotSyncWorkerMain(Datum main_arg)
 		int			rc;
 		long		naptime;
 
-		ProcessSlotSyncInterrupts(&wrconn);
+		ProcessSlotSyncInterrupts(&wrconn, NULL /* standby_slots */ );
 
 		naptime = synchronize_slots(wrconn);
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index edb3656cd2..db5af0b598 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1608,7 +1608,7 @@ PhysicalWakeupLogicalWalSnd(void)
  * Reload the config file and reinitialize the standby slot list if the GUC
  * standby_slot_names has changed.
  */
-static void
+void
 WalSndRereadConfigAndReInitSlotList(List **standby_slots)
 {
 	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
@@ -1631,13 +1631,12 @@ WalSndRereadConfigAndReInitSlotList(List **standby_slots)
  * This function updates the passed standby_slots list, removing any slots that
  * have already caught up to or surpassed the given wait_for_lsn.
  */
-static void
+void
 WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 {
 	ListCell   *lc;
-	List	   *standby_slots_cpy = *standby_slots;
 
-	foreach(lc, standby_slots_cpy)
+	foreach(lc, *standby_slots)
 	{
 		char	   *name = lfirst(lc);
 		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
@@ -1704,10 +1703,8 @@ WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 		if (warningfmt)
 			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
 
-		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+		*standby_slots = foreach_delete_current(*standby_slots, lc);
 	}
-
-	*standby_slots = standby_slots_cpy;
 }
 
 /*
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 1fcc22a127..c5c4714788 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -15,6 +15,7 @@
 #include <signal.h>
 
 #include "access/xlogdefs.h"
+#include "nodes/pg_list.h"
 
 /*
  * What to do with a snapshot in create replication slot command.
@@ -50,7 +51,11 @@ extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn,
+									 List **standby_slots);
 extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
+extern List *WalSndGetStandbySlots(void);
+extern void WalSndRereadConfigAndReInitSlotList(List **standby_slots);
 
 /*
  * Remember that we want to wakeup walsenders later
-- 
2.34.1



  [application/octet-stream] v40-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (134.3K, ../../CAJpy0uAa6HnPKCO8pM7xFfEmzXtXDmgRvX5ErJX8e8-mWyvXYQ@mail.gmail.com/4-v40-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From 1916bce2e9de5f0085630cfdaefab4ff2a5550f6 Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Tue, 21 Nov 2023 14:50:21 +0530
Subject: [PATCH v40 1/3] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to the
physical standbys so that logical replication can be resumed after failover. It
is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API. Examples:

CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

(failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of pg_replication_slots
view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).

A new walreceiver API walrcv_alter_slot has been introduced to
enable the failover of the slot on publisher node.
---
 contrib/test_decoding/expected/slot.out       |  58 ++++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |   9 +-
 doc/src/sgml/ref/create_subscription.sgml     |  23 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       |  91 ++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  44 ++-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  51 ++-
 src/backend/replication/logical/worker.c      |  72 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 182 +++++++++-
 src/backend/replication/slotfuncs.c           |  19 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 327 +++++++++++++++++-
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_upgrade/info.c                     |   6 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 src/test/recovery/t/050_verify_slot_order.pl  | 149 ++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 +++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 48 files changed, 1334 insertions(+), 163 deletions(-)
 create mode 100644 src/test/recovery/t/050_verify_slot_order.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 93735e3aea..6440378d5c 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4344,6 +4344,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..056404af5d 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..fa6cd1c43f 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,29 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..c3187e7b9a 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,29 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If only the slot_name is specified (without create_slot option),
+			 * it is possible that the user intends to use an existing slot on
+			 * the publisher, so here we enable failover for the slot if
+			 * requested.
+			 */
+			else if (opts.slot_name && failover_enabled)
+			{
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("enabled failover for replication slot \"%s\" on publisher",
+								opts.slot_name)));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,8 +1326,8 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
@@ -1288,6 +1335,12 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when failover is enabled"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
 
 					/* Make sure refresh sees the new list of publications. */
@@ -1334,8 +1387,8 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
@@ -1347,6 +1400,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
 
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when failover is enabled"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
 
 					/* Refresh the new list of publications. */
@@ -1392,6 +1455,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when failover is enabled"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
 
 				AlterSubscription_refresh(sub, opts.copy_data, NULL);
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 60d5c1fc40..336c2bec99 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -883,8 +887,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -913,7 +917,14 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+		{
+			appendStringInfoString(&cmd, "FAILOVER");
+			if (use_new_options_syntax)
+				appendStringInfoString(&cmd, ", ");
+			else
+				appendStringInfoChar(&cmd, ' ');
+		}
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -982,6 +993,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					 bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index df3c42eb5d..1eaee4197b 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -614,15 +614,26 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
+									MySubscription->name)));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that failover can be enabled",
+									MySubscription->name)));
+
 				should_exit = true;
 			}
 		}
@@ -1420,7 +1431,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1722,10 +1734,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1733,9 +1747,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1749,9 +1762,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..0b7d632922 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,9 +132,41 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * the failover = true when creating the subscription. Enabling failover allows
+ * us to smoothly transition to the standby in case the primary gets promoted,
+ * ensuring that we can subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby. During syncing, the local restart_lsn and/or local catalog_xmin of
+ * the newly created slot on the standby are typically ahead of those on the
+ * primary. Therefore, the standby needs to wait for the primary server's
+ * restart_lsn and catalog_xmin to catch up, which takes time.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
+
 #include "postgres.h"
 
 #include <sys/stat.h>
@@ -3947,6 +3979,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4515,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4573,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4613,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..c96a1700cd 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List	   *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -251,7 +263,8 @@ ReplicationSlotValidateName(const char *name, int elevel)
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +324,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +693,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2198,140 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+
+	/* Need a modifiable copy of string */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into list of identifiers */
+	if (!SplitIdentifierString(rawname, ',', &elemlist))
+	{
+		/* syntax error in name list */
+		GUC_check_errdetail("List syntax is invalid.");
+		goto ret_standby_slot_names_ng;
+	}
+
+	/*
+	 * Verify 'type' of slot now.
+	 *
+	 * Skip check if replication slots' data is not initialized yet i.e. we
+	 * are in startup process.
+	 */
+	if (!ReplicationSlotCtl)
+		goto ret_standby_slot_names_ok;
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+			goto ret_standby_slot_names_ng;
+
+		if (SlotIsLogical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			goto ret_standby_slot_names_ng;
+		}
+	}
+
+ret_standby_slot_names_ok:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return true;
+
+ret_standby_slot_names_ng:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return false;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..fb6e37d2c3 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -679,6 +689,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +745,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +785,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 2398167f49..e27d231174 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -386,7 +386,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..3e44228bde 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool	failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,233 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up logical walsenders with failover-enabled slots if the physical slot
+ * of the current walsender is specified in standby_slot_names GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
+		bool		invalidated = false;
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (slot && SlotIsPhysical(slot))
+		{
+			SpinLockAcquire(&slot->mutex);
+			restart_lsn = slot->data.restart_lsn;
+			invalidated = slot->data.invalidated != RS_INVAL_NONE;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Continue if the current slot hasn't caught up. */
+		if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
+			restart_lsn < wait_for_lsn)
+		{
+			/* Log warning if no active_pid for this physical slot */
+			if (slot->active_pid == 0)
+				ereport(WARNING,
+						errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+							   name, "standby_slot_names"),
+						errdetail("Logical replication is waiting on the "
+								  "standby associated with \"%s\"", name),
+						errhint("Consider starting standby associated with "
+								"\"%s\" or amend standby_slot_names", name));
+
+			continue;
+		}
+
+		/*
+		 * It may happen that the slot specified in standby_slot_names GUC
+		 * value is dropped, so let's skip over it.
+		 */
+		else if (!slot)
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+
+		/*
+		 * If logical slot name is given in standby_slot_names, give WARNING
+		 * and skip it. Since it is harmless, so WARNING should be enough, no
+		 * need to error-out.
+		 */
+		else if (SlotIsLogical(slot))
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+
+		/*
+		 * Specified physical slot may have been invalidated, so no point in
+		 * waiting for it.
+		 */
+		else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
+			warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+		else
+			Assert(restart_lsn >= wait_for_lsn);
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, the
+ * function also waits for all the specified streaming replication standby
+ * servers to confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr if we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
 	if (RecentFlushPtr != InvalidXLogRecPtr &&
 		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know we
+		 * have enough WAL available and all the standby servers have confirmed
+		 * receipt of WAL up to RecentFlushPtr. This is particularly interesting
+		 * if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1825,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1840,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,8 +1879,14 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
 			break;
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
@@ -1654,9 +1927,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2094,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2331,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3594,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3665,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another CV
+	 * that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b764ef6998..e5e7bb23f9 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4562,6 +4562,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e48c066a5b..998080e0e4 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -326,6 +326,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..a348932e62 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,14 +679,17 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
+
 		slotinfos = (LogicalSlotInfo *) pg_malloc(sizeof(LogicalSlotInfo) * num_slots);
 
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +700,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..f6ac78418e 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced
+								 * to the physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 5b01cf8c40..0a1c467ed0 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -158,7 +158,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -172,8 +172,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 006e10f5d2..46e875c48b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3306,7 +3306,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb58dee3bc..d906734750 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..ca06e5b1ad 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)?
+	 * Only relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 04b439dc50..61bc8de72c 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -356,9 +356,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									   const char *slotname,
+									   bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -400,6 +411,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -429,8 +441,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 268f8e8d0f..1fcc22a127 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -14,6 +14,8 @@
 
 #include <signal.h>
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -47,6 +49,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 47854b5cd4..4378690ab0 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -258,7 +258,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..3be3ee52fc 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_verify_slot_order.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
new file mode 100644
index 0000000000..bff0f52a46
--- /dev/null
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -0,0 +1,149 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres', "CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+$subscriber1->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true
+$subscriber1->safe_psql('postgres',
+		"CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);");
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+$subscriber2->safe_psql('postgres',
+		"CREATE SUBSCRIPTION mysub2 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);");
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby comes
+# up.
+$standby1->stop;
+
+# Create some data on primary
+my $primary_row_count = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = 0 FROM tab_int;");
+is($result, 't', "subscriber1 doesn't get data from primary until standby1 acknowledges changes");
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 86a9886d4f..d80d30e99c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3862,6 +3863,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 06:49               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-28 09:40                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-11-28 09:44                   ` shveta malik <[email protected]>
  1 sibling, 0 replies; 113+ messages in thread

From: shveta malik @ 2023-11-28 09:44 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Tue, Nov 28, 2023 at 3:10 PM shveta malik <[email protected]> wrote:
>
> On Tue, Nov 28, 2023 at 12:19 PM Drouvot, Bertrand
> <[email protected]> wrote:
> >
> > Hi,
> >
> > On 11/28/23 4:13 AM, shveta malik wrote:
> > > On Mon, Nov 27, 2023 at 4:08 PM Amit Kapila <[email protected]> wrote:
> > >>
> > >> On Mon, Nov 27, 2023 at 2:27 PM Zhijie Hou (Fujitsu)
> > >> <[email protected]> wrote:
> > >>>
> > >>> Here is the updated version(v39_2) which include all the changes made in 0002.
> > >>> Please use for review, and sorry for the confusion.
> > >>>
> > >>
> > >> --- a/src/backend/replication/logical/launcher.c
> > >> +++ b/src/backend/replication/logical/launcher.c
> > >> @@ -8,20 +8,27 @@
> > >>    *   src/backend/replication/logical/launcher.c
> > >>    *
> > >>    * NOTES
> > >> - *   This module contains the logical replication worker launcher which
> > >> - *   uses the background worker infrastructure to start the logical
> > >> - *   replication workers for every enabled subscription.
> > >> + *   This module contains the replication worker launcher which
> > >> + *   uses the background worker infrastructure to:
> > >> + *   a) start the logical replication workers for every enabled subscription
> > >> + *      when not in standby_mode.
> > >> + *   b) start the slot sync worker for logical failover slots synchronization
> > >> + *      from the primary server when in standby_mode.
> > >>
> > >> I was wondering do we really need a launcher on standby to invoke
> > >> sync-slot worker. If so, why? I guess it may be required for previous
> > >> versions where we were managing work for multiple slot-sync workers
> > >> which is also questionable in the sense of whether launcher is the
> > >> right candidate for the same but now with the single slot-sync worker,
> > >> it doesn't seem worth having it. What do you think?
> > >>
> > >> --
> > >
> > > Yes, earlier a manager process was needed to manage multiple slot-sync
> > > workers and distribute load among them, but now that does not seem
> > > necessary. I gave it a try (PoC) and it seems to work well.  If  there
> > > are no objections to this approach, I can share the patch soon.
> > >
> >
> > +1 on this new approach, thanks!
>
> PFA v40. This patch has removed Logical Replication Launcher support
> to launch slotsync worker.  The slot-sync worker is now registered as
> bgworker with postmaster, with
> bgw_start_time=BgWorkerStart_ConsistentState and
> bgw_restart_time=60sec.
>
> On removal of launcher, now all the validity checks have been shifted
> to slot-sync worker itself.  This brings us to some point of concerns:
>
> a) We still need to maintain  RecoveryInProgress() check in slotsync
> worker. Since worker has the start time of
> BgWorkerStart_ConsistentState, it will be started on non-standby as
> well. So to ensure that it exists on non-standby, "RecoveryInProgress"
> has been introduced at the beginning of the worker. But once it exits,
> postmaster will not restart it since it will be clean-exist i.e.
> proc_exit(0) (the restart logic of postmaster comes into play only
> when there is an abnormal exit). But to exit for the first time on
> non-standby, we need that Recovery related check in worker.
>
> b) "enable_syncslot" check is moved to slotsync worker now. Since
> enable_syncslot is PGC_SIGHUP, so proc_exit(1) is currently used to
> exit the worker if 'enable_syncslot' is found to be disabled.
> 'proc_exit(1)' has been used in order to ensure that the worker is
> restarted and GUCs are checked again after restart_time. Downside of
> this approach is, if someone has kept "enable_syncslot" as disabled
> permanently even on standby, slotsync worker will keep on restarting
> and exiting.
>
> So to overcome the above pain-points, I think a potential approach
> will be to start slotsync worker only if 'enable_syncslot' is on and
> the system is non-standby. Potential ways (each with some issues) are:
>

Correction here:  start slotsync worker only if 'enable_syncslot' is
on and the system is standby.

> 1) Use the current way i.e. register slot-sync worker as bgworker with
> postmaster, but introduce extra checks in 'maybe_start_bgworkers'. But
> this seems more like a hack. This will need extra changes as currently
> once 'maybe_start_bgworkers' is attempted by postmaster, it will
> attempt again to start any worker only if the worker had abnormal exit
> and restart_time !=0. The current postmatser will not attempt to start
> worker on any GUC change.
>
> 2) Another way maybe to treat slotsync worker as special case and
> separate out the start/restart of slotsync worker from bgworker, and
> follow what we do for autovacuum launcher(StartAutoVacLauncher) to
> keep starting it in the postmaster loop(ServerLoop). In this way, we
> may be able to add more checks before starting worker. But by opting
> this approach, we will have to manage slotsync worker completely by
> ourself as it will be no longer be part of existing
> bgworker-registration infra. If this seems okay and there are no other
> better options, it can be analyzed further in detail.
>
> 3) Another approach could be, in order to solve issue (a), introduce a
> new start_time 'BgWorkerStart_ConsistentState_HotStandby' which means
> start a bgworker only if consistent state is reached and the system is
> standby. And for issue (b), lets retain check of enable_syncslot in
> the worker itself but make it 'PGC_POSTMASTER'. This will ensure we
> can safely exit the worker(proc_exit(0) if enable_syncslot is disabled
> and postmaster will not restart it. But I'm not sure if making it
> "PGC_POSTMASTER" is acceptable from the user's perspective.
>
> thanks
> Shveta






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 06:49               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-28 09:40                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-11-28 15:58                   ` Drouvot, Bertrand <[email protected]>
  2023-11-29 04:17                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-29 09:11                     ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  1 sibling, 2 replies; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-11-28 15:58 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 11/28/23 10:40 AM, shveta malik wrote:
> On Tue, Nov 28, 2023 at 12:19 PM Drouvot, Bertrand
> <[email protected]> wrote:
>>
>> Hi,
>>
>> On 11/28/23 4:13 AM, shveta malik wrote:
>>> On Mon, Nov 27, 2023 at 4:08 PM Amit Kapila <[email protected]> wrote:
>>>>
>>>> On Mon, Nov 27, 2023 at 2:27 PM Zhijie Hou (Fujitsu)
>>>> <[email protected]> wrote:
>>>>>
>>>>> Here is the updated version(v39_2) which include all the changes made in 0002.
>>>>> Please use for review, and sorry for the confusion.
>>>>>
>>>>
>>>> --- a/src/backend/replication/logical/launcher.c
>>>> +++ b/src/backend/replication/logical/launcher.c
>>>> @@ -8,20 +8,27 @@
>>>>     *   src/backend/replication/logical/launcher.c
>>>>     *
>>>>     * NOTES
>>>> - *   This module contains the logical replication worker launcher which
>>>> - *   uses the background worker infrastructure to start the logical
>>>> - *   replication workers for every enabled subscription.
>>>> + *   This module contains the replication worker launcher which
>>>> + *   uses the background worker infrastructure to:
>>>> + *   a) start the logical replication workers for every enabled subscription
>>>> + *      when not in standby_mode.
>>>> + *   b) start the slot sync worker for logical failover slots synchronization
>>>> + *      from the primary server when in standby_mode.
>>>>
>>>> I was wondering do we really need a launcher on standby to invoke
>>>> sync-slot worker. If so, why? I guess it may be required for previous
>>>> versions where we were managing work for multiple slot-sync workers
>>>> which is also questionable in the sense of whether launcher is the
>>>> right candidate for the same but now with the single slot-sync worker,
>>>> it doesn't seem worth having it. What do you think?
>>>>
>>>> --
>>>
>>> Yes, earlier a manager process was needed to manage multiple slot-sync
>>> workers and distribute load among them, but now that does not seem
>>> necessary. I gave it a try (PoC) and it seems to work well.  If  there
>>> are no objections to this approach, I can share the patch soon.
>>>
>>
>> +1 on this new approach, thanks!
> 
> PFA v40. This patch has removed Logical Replication Launcher support
> to launch slotsync worker.

Thanks!

>  The slot-sync worker is now registered as
> bgworker with postmaster, with
> bgw_start_time=BgWorkerStart_ConsistentState and
> bgw_restart_time=60sec.
> 
> On removal of launcher, now all the validity checks have been shifted
> to slot-sync worker itself.  This brings us to some point of concerns:
> 
> a) We still need to maintain  RecoveryInProgress() check in slotsync
> worker. Since worker has the start time of
> BgWorkerStart_ConsistentState, it will be started on non-standby as
> well. So to ensure that it exists on non-standby, "RecoveryInProgress"
> has been introduced at the beginning of the worker. But once it exits,
> postmaster will not restart it since it will be clean-exist i.e.
> proc_exit(0) (the restart logic of postmaster comes into play only
> when there is an abnormal exit). But to exit for the first time on
> non-standby, we need that Recovery related check in worker.
> 
> b) "enable_syncslot" check is moved to slotsync worker now. Since
> enable_syncslot is PGC_SIGHUP, so proc_exit(1) is currently used to
> exit the worker if 'enable_syncslot' is found to be disabled.
> 'proc_exit(1)' has been used in order to ensure that the worker is
> restarted and GUCs are checked again after restart_time. Downside of
> this approach is, if someone has kept "enable_syncslot" as disabled
> permanently even on standby, slotsync worker will keep on restarting
> and exiting.
> 
> So to overcome the above pain-points, I think a potential approach
> will be to start slotsync worker only if 'enable_syncslot' is on and
> the system is non-standby. 

That makes sense to me.

> Potential ways (each with some issues) are:
> 
> 1) Use the current way i.e. register slot-sync worker as bgworker with
> postmaster, but introduce extra checks in 'maybe_start_bgworkers'. But
> this seems more like a hack. This will need extra changes as currently
> once 'maybe_start_bgworkers' is attempted by postmaster, it will
> attempt again to start any worker only if the worker had abnormal exit
> and restart_time !=0. The current postmatser will not attempt to start
> worker on any GUC change.
> 
> 2) Another way maybe to treat slotsync worker as special case and
> separate out the start/restart of slotsync worker from bgworker, and
> follow what we do for autovacuum launcher(StartAutoVacLauncher) to
> keep starting it in the postmaster loop(ServerLoop). In this way, we
> may be able to add more checks before starting worker. But by opting
> this approach, we will have to manage slotsync worker completely by
> ourself as it will be no longer be part of existing
> bgworker-registration infra. If this seems okay and there are no other
> better options, it can be analyzed further in detail.
> 
> 3) Another approach could be, in order to solve issue (a), introduce a
> new start_time 'BgWorkerStart_ConsistentState_HotStandby' which means
> start a bgworker only if consistent state is reached and the system is
> standby. And for issue (b), lets retain check of enable_syncslot in
> the worker itself but make it 'PGC_POSTMASTER'. This will ensure we
> can safely exit the worker(proc_exit(0) if enable_syncslot is disabled
> and postmaster will not restart it. But I'm not sure if making it
> "PGC_POSTMASTER" is acceptable from the user's perspective.

I had the same idea (means make enable_syncslot as 'PGC_POSTMASTER')
when reading b). I'm +1 on it (at least for V1) as I don't think that
this parameter value would change frequently. Curious to know what others
think too.

Then as far a) is concerned, I'd vote for introducing a new
BgWorkerStart_ConsistentState_HotStandby.

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 06:49               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-28 09:40                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 15:58                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-11-29 04:17                     ` shveta malik <[email protected]>
  1 sibling, 0 replies; 113+ messages in thread

From: shveta malik @ 2023-11-29 04:17 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Tue, Nov 28, 2023 at 9:28 PM Drouvot, Bertrand
<[email protected]> wrote:
>
> Hi,
>
> On 11/28/23 10:40 AM, shveta malik wrote:
> > On Tue, Nov 28, 2023 at 12:19 PM Drouvot, Bertrand
> > <[email protected]> wrote:
> >>
> >> Hi,
> >>
> >> On 11/28/23 4:13 AM, shveta malik wrote:
> >>> On Mon, Nov 27, 2023 at 4:08 PM Amit Kapila <[email protected]> wrote:
> >>>>
> >>>> On Mon, Nov 27, 2023 at 2:27 PM Zhijie Hou (Fujitsu)
> >>>> <[email protected]> wrote:
> >>>>>
> >>>>> Here is the updated version(v39_2) which include all the changes made in 0002.
> >>>>> Please use for review, and sorry for the confusion.
> >>>>>
> >>>>
> >>>> --- a/src/backend/replication/logical/launcher.c
> >>>> +++ b/src/backend/replication/logical/launcher.c
> >>>> @@ -8,20 +8,27 @@
> >>>>     *   src/backend/replication/logical/launcher.c
> >>>>     *
> >>>>     * NOTES
> >>>> - *   This module contains the logical replication worker launcher which
> >>>> - *   uses the background worker infrastructure to start the logical
> >>>> - *   replication workers for every enabled subscription.
> >>>> + *   This module contains the replication worker launcher which
> >>>> + *   uses the background worker infrastructure to:
> >>>> + *   a) start the logical replication workers for every enabled subscription
> >>>> + *      when not in standby_mode.
> >>>> + *   b) start the slot sync worker for logical failover slots synchronization
> >>>> + *      from the primary server when in standby_mode.
> >>>>
> >>>> I was wondering do we really need a launcher on standby to invoke
> >>>> sync-slot worker. If so, why? I guess it may be required for previous
> >>>> versions where we were managing work for multiple slot-sync workers
> >>>> which is also questionable in the sense of whether launcher is the
> >>>> right candidate for the same but now with the single slot-sync worker,
> >>>> it doesn't seem worth having it. What do you think?
> >>>>
> >>>> --
> >>>
> >>> Yes, earlier a manager process was needed to manage multiple slot-sync
> >>> workers and distribute load among them, but now that does not seem
> >>> necessary. I gave it a try (PoC) and it seems to work well.  If  there
> >>> are no objections to this approach, I can share the patch soon.
> >>>
> >>
> >> +1 on this new approach, thanks!
> >
> > PFA v40. This patch has removed Logical Replication Launcher support
> > to launch slotsync worker.
>
> Thanks!
>
> >  The slot-sync worker is now registered as
> > bgworker with postmaster, with
> > bgw_start_time=BgWorkerStart_ConsistentState and
> > bgw_restart_time=60sec.
> >
> > On removal of launcher, now all the validity checks have been shifted
> > to slot-sync worker itself.  This brings us to some point of concerns:
> >
> > a) We still need to maintain  RecoveryInProgress() check in slotsync
> > worker. Since worker has the start time of
> > BgWorkerStart_ConsistentState, it will be started on non-standby as
> > well. So to ensure that it exists on non-standby, "RecoveryInProgress"
> > has been introduced at the beginning of the worker. But once it exits,
> > postmaster will not restart it since it will be clean-exist i.e.
> > proc_exit(0) (the restart logic of postmaster comes into play only
> > when there is an abnormal exit). But to exit for the first time on
> > non-standby, we need that Recovery related check in worker.
> >
> > b) "enable_syncslot" check is moved to slotsync worker now. Since
> > enable_syncslot is PGC_SIGHUP, so proc_exit(1) is currently used to
> > exit the worker if 'enable_syncslot' is found to be disabled.
> > 'proc_exit(1)' has been used in order to ensure that the worker is
> > restarted and GUCs are checked again after restart_time. Downside of
> > this approach is, if someone has kept "enable_syncslot" as disabled
> > permanently even on standby, slotsync worker will keep on restarting
> > and exiting.
> >
> > So to overcome the above pain-points, I think a potential approach
> > will be to start slotsync worker only if 'enable_syncslot' is on and
> > the system is non-standby.
>
> That makes sense to me.
>
> > Potential ways (each with some issues) are:
> >
> > 1) Use the current way i.e. register slot-sync worker as bgworker with
> > postmaster, but introduce extra checks in 'maybe_start_bgworkers'. But
> > this seems more like a hack. This will need extra changes as currently
> > once 'maybe_start_bgworkers' is attempted by postmaster, it will
> > attempt again to start any worker only if the worker had abnormal exit
> > and restart_time !=0. The current postmatser will not attempt to start
> > worker on any GUC change.
> >
> > 2) Another way maybe to treat slotsync worker as special case and
> > separate out the start/restart of slotsync worker from bgworker, and
> > follow what we do for autovacuum launcher(StartAutoVacLauncher) to
> > keep starting it in the postmaster loop(ServerLoop). In this way, we
> > may be able to add more checks before starting worker. But by opting
> > this approach, we will have to manage slotsync worker completely by
> > ourself as it will be no longer be part of existing
> > bgworker-registration infra. If this seems okay and there are no other
> > better options, it can be analyzed further in detail.
> >
> > 3) Another approach could be, in order to solve issue (a), introduce a
> > new start_time 'BgWorkerStart_ConsistentState_HotStandby' which means
> > start a bgworker only if consistent state is reached and the system is
> > standby. And for issue (b), lets retain check of enable_syncslot in
> > the worker itself but make it 'PGC_POSTMASTER'. This will ensure we
> > can safely exit the worker(proc_exit(0) if enable_syncslot is disabled
> > and postmaster will not restart it. But I'm not sure if making it
> > "PGC_POSTMASTER" is acceptable from the user's perspective.
>
> I had the same idea (means make enable_syncslot as 'PGC_POSTMASTER')
> when reading b). I'm +1 on it (at least for V1) as I don't think that
> this parameter value would change frequently. Curious to know what others
> think too.
>
> Then as far a) is concerned, I'd vote for introducing a new
> BgWorkerStart_ConsistentState_HotStandby.
>

+1 on PGC_POSTMASTER and BgWorkerStart_ConsistentState_HotStandby. A
clean solution as compared to the rest of the approaches. Will
implement it.

thanks
Shveta






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 06:49               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-28 09:40                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 15:58                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-11-29 09:11                     ` Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-30 07:05                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-01 04:10                       ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  1 sibling, 2 replies; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-11-29 09:11 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Tuesday, November 28, 2023 11:58 PM Drouvot, Bertrand <[email protected]> wrote:
> 
> Hi,
> 
> On 11/28/23 10:40 AM, shveta malik wrote:
> > On Tue, Nov 28, 2023 at 12:19 PM Drouvot, Bertrand
> > <[email protected]> wrote:
> >>
> >> Hi,
> >>
> >> On 11/28/23 4:13 AM, shveta malik wrote:
> >>> On Mon, Nov 27, 2023 at 4:08 PM Amit Kapila
> <[email protected]> wrote:
> >>>>
> >>>> On Mon, Nov 27, 2023 at 2:27 PM Zhijie Hou (Fujitsu)
> >>>> <[email protected]> wrote:
> >>>>>
> >>>>> Here is the updated version(v39_2) which include all the changes made
> in 0002.
> >>>>> Please use for review, and sorry for the confusion.
> >>>>>
> >>>>
> >>>> --- a/src/backend/replication/logical/launcher.c
> >>>> +++ b/src/backend/replication/logical/launcher.c
> >>>> @@ -8,20 +8,27 @@
> >>>>     *   src/backend/replication/logical/launcher.c
> >>>>     *
> >>>>     * NOTES
> >>>> - *   This module contains the logical replication worker launcher which
> >>>> - *   uses the background worker infrastructure to start the logical
> >>>> - *   replication workers for every enabled subscription.
> >>>> + *   This module contains the replication worker launcher which
> >>>> + *   uses the background worker infrastructure to:
> >>>> + *   a) start the logical replication workers for every enabled
> subscription
> >>>> + *      when not in standby_mode.
> >>>> + *   b) start the slot sync worker for logical failover slots
> synchronization
> >>>> + *      from the primary server when in standby_mode.
> >>>>
> >>>> I was wondering do we really need a launcher on standby to invoke
> >>>> sync-slot worker. If so, why? I guess it may be required for
> >>>> previous versions where we were managing work for multiple
> >>>> slot-sync workers which is also questionable in the sense of
> >>>> whether launcher is the right candidate for the same but now with
> >>>> the single slot-sync worker, it doesn't seem worth having it. What do you
> think?
> >>>>
> >>>> --
> >>>
> >>> Yes, earlier a manager process was needed to manage multiple
> >>> slot-sync workers and distribute load among them, but now that does
> >>> not seem necessary. I gave it a try (PoC) and it seems to work well.
> >>> If  there are no objections to this approach, I can share the patch soon.
> >>>
> >>
> >> +1 on this new approach, thanks!
> >
> > PFA v40. This patch has removed Logical Replication Launcher support
> > to launch slotsync worker.
> 
> Thanks!
> 
> >  The slot-sync worker is now registered as bgworker with postmaster,
> > with bgw_start_time=BgWorkerStart_ConsistentState and
> > bgw_restart_time=60sec.
> >
> > On removal of launcher, now all the validity checks have been shifted
> > to slot-sync worker itself.  This brings us to some point of concerns:
> >
> > a) We still need to maintain  RecoveryInProgress() check in slotsync
> > worker. Since worker has the start time of
> > BgWorkerStart_ConsistentState, it will be started on non-standby as
> > well. So to ensure that it exists on non-standby, "RecoveryInProgress"
> > has been introduced at the beginning of the worker. But once it exits,
> > postmaster will not restart it since it will be clean-exist i.e.
> > proc_exit(0) (the restart logic of postmaster comes into play only
> > when there is an abnormal exit). But to exit for the first time on
> > non-standby, we need that Recovery related check in worker.
> >
> > b) "enable_syncslot" check is moved to slotsync worker now. Since
> > enable_syncslot is PGC_SIGHUP, so proc_exit(1) is currently used to
> > exit the worker if 'enable_syncslot' is found to be disabled.
> > 'proc_exit(1)' has been used in order to ensure that the worker is
> > restarted and GUCs are checked again after restart_time. Downside of
> > this approach is, if someone has kept "enable_syncslot" as disabled
> > permanently even on standby, slotsync worker will keep on restarting
> > and exiting.
> >
> > So to overcome the above pain-points, I think a potential approach
> > will be to start slotsync worker only if 'enable_syncslot' is on and
> > the system is non-standby.
> 
> That makes sense to me.
> 
> > Potential ways (each with some issues) are:
> >
> > 1) Use the current way i.e. register slot-sync worker as bgworker with
> > postmaster, but introduce extra checks in 'maybe_start_bgworkers'. But
> > this seems more like a hack. This will need extra changes as currently
> > once 'maybe_start_bgworkers' is attempted by postmaster, it will
> > attempt again to start any worker only if the worker had abnormal exit
> > and restart_time !=0. The current postmatser will not attempt to start
> > worker on any GUC change.
> >
> > 2) Another way maybe to treat slotsync worker as special case and
> > separate out the start/restart of slotsync worker from bgworker, and
> > follow what we do for autovacuum launcher(StartAutoVacLauncher) to
> > keep starting it in the postmaster loop(ServerLoop). In this way, we
> > may be able to add more checks before starting worker. But by opting
> > this approach, we will have to manage slotsync worker completely by
> > ourself as it will be no longer be part of existing
> > bgworker-registration infra. If this seems okay and there are no other
> > better options, it can be analyzed further in detail.
> >
> > 3) Another approach could be, in order to solve issue (a), introduce a
> > new start_time 'BgWorkerStart_ConsistentState_HotStandby' which means
> > start a bgworker only if consistent state is reached and the system is
> > standby. And for issue (b), lets retain check of enable_syncslot in
> > the worker itself but make it 'PGC_POSTMASTER'. This will ensure we
> > can safely exit the worker(proc_exit(0) if enable_syncslot is disabled
> > and postmaster will not restart it. But I'm not sure if making it
> > "PGC_POSTMASTER" is acceptable from the user's perspective.
> 
> I had the same idea (means make enable_syncslot as 'PGC_POSTMASTER')
> when reading b). I'm +1 on it (at least for V1) as I don't think that this parameter
> value would change frequently. Curious to know what others think too.
> 
> Then as far a) is concerned, I'd vote for introducing a new
> BgWorkerStart_ConsistentState_HotStandby.

Here is the V41 patch set which includes the following changes.

V41-0001:
1) Based on the discussion[1], I update the document to remind user to
change the slot's failover option when ALTER SUBSCRIPTION ... SET
(slot_name = xx).

2) Address comments in [2][3][4].

V41-0002:
1) 'enable_syncslot' is changed from PGC_SIGHUP to PGC_POSTMASTER,
slot-sync worker will now clean exit (proc_exit(0)) if enable_syncslot is
found disabled.

2) BgWorkerStart_ConsistentState_HotStandby is introduced as new
start-time for bgworker. This will start worker only if it is standby_mode
and consistent state is reached.

3) 'SYNCSLOT_STATE_INITIATED' is now set in 'ReplicationSlotCreate' itself
in slot-sync worker case. Earlier it was set at later point of time giving
a window wherein even a synced slot was in 'n' state for quite some time,
which was not correct.

Thanks Shveta for working on the V41-0002.

[1] https://www.postgresql.org/message-id/CAA4eK1Jd9dk%3D5POTKM9p4EyYqYzLXe-AnLzHrUELjzZScLz7mw%40mail.g...
[2] https://www.postgresql.org/message-id/eb09f682-db82-41cd-93bc-5d44e10e1d6d%40gmail.com
[3] https://www.postgresql.org/message-id/CAHut%2BPsuSWjm7U_sVnL8FXZ7ZQcfCcT44kAK7i6qMG35Cwjy3A%40mail.g...
[4] https://www.postgresql.org/message-id/CAFPTHDbFqLgXS6Et%2BshNGPDjCKK66C%2BZSarqFHmQvfnAah3Qsw%40mail...

Best Regards,
Hou zj


Attachments:

  [application/octet-stream] v41-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch (7.8K, ../../OS0PR01MB57163425CEE6CEC16C7D6FA59483A@OS0PR01MB5716.jpnprd01.prod.outlook.com/2-v41-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch)
  download | inline diff:
From 61984463efbfabf3c182a16aa855ac6e94617bae Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Wed, 29 Nov 2023 16:38:36 +0800
Subject: [PATCH v41 3/3] Allow slot-sync worker to wait for the cascading
 standbys.

The GUC standby_slot_names is needed to be set on first standby
in order to allow it to wait for confirmation for cascading
standbys before updating logical 'synced' slots in slot-sync worker.
The intent is that the logical slots (synced ones) should not go
ahead of cascading standbys.

For the user created slots on first standby, we already have this wait
logic in place in logical walsender and in pg_logical_slot_get_changes_guts(),
but for synced slots (which can not be consumed yet), we need to make
sure that they are not going ahead of cascading standbys and that is
acheived by introducing the wait in slot-sync worker before we actually
update the slots.
---
 src/backend/replication/logical/slotsync.c | 89 ++++++++++++++++++++--
 src/backend/replication/walsender.c        |  4 +-
 src/include/replication/walsender.h        |  5 ++
 3 files changed, 88 insertions(+), 10 deletions(-)

diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index ea1e333c70..3638b3156d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -108,7 +108,9 @@ static TimestampTz last_update_time;
  */
 #define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
 
-static void ProcessSlotSyncInterrupts(WalReceiverConn **wrconn);
+static void ProcessSlotSyncInterrupts(WalReceiverConn **wrconn,
+									  List **standby_slots);
+
 
 /*
  * Wait for remote slot to pass locally reserved position.
@@ -164,7 +166,7 @@ wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
 		CHECK_FOR_INTERRUPTS();
 
 		/* Handle any termination request if any */
-		ProcessSlotSyncInterrupts(&wrconn);
+		ProcessSlotSyncInterrupts(&wrconn, NULL /* standby_slots */ );
 
 		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
 
@@ -479,6 +481,52 @@ construct_slot_query(StringInfo s)
 					 " WHERE failover and sync_state != 'i'");
 }
 
+/*
+ * Wait for cascading physical standbys corresponding to physical slots
+ * specified in standby_slot_names GUC to confirm receiving given lsn.
+ */
+static void
+wait_for_standby_confirmation(XLogRecPtr wait_for_lsn,
+							  WalReceiverConn *wrconn)
+{
+	List	   *standby_slots;
+
+	/* Nothing to be done */
+	if (strcmp(standby_slot_names, "") == 0)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	for (;;)
+	{
+		int			rc;
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		/*
+		 * This will reload configuration and will refresh the standby_slots
+		 * as well provided standby_slot_names GUC is changed by the user.
+		 */
+		ProcessSlotSyncInterrupts(&wrconn, &standby_slots);
+
+		/*
+		 * XXX: Is waiting for 5 second before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   5000L,
+					   WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+}
+
 /*
  * Synchronize single slot to given position.
  *
@@ -747,6 +795,7 @@ synchronize_slots(WalReceiverConn *wrconn)
 	List	   *remote_slot_list = NIL;
 	MemoryContext oldctx = CurrentMemoryContext;
 	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	XLogRecPtr	max_confirmed_lsn = 0;
 	ListCell   *cell;
 	bool		slot_updated = false;
 	TimestampTz now;
@@ -799,6 +848,9 @@ synchronize_slots(WalReceiverConn *wrconn)
 		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
 		Assert(!isnull);
 
+		if (remote_slot->confirmed_lsn > max_confirmed_lsn)
+			max_confirmed_lsn = remote_slot->confirmed_lsn;
+
 		/*
 		 * It is possible to get null values for LSN and Xmin if slot is
 		 * invalidated on the primary server, so handle accordingly.
@@ -842,6 +894,17 @@ synchronize_slots(WalReceiverConn *wrconn)
 	 */
 	drop_obsolete_slots(remote_slot_list);
 
+	/*
+	 * If there are cascading standbys, wait for their confirmation before we
+	 * update synced logical slots locally.
+	 *
+	 * Instead of waiting on confirmation for lsn of each slot, let us wait
+	 * once for confirmation on max_confirmed_lsn. If that is confirmed by
+	 * each cascading standby, we are good to update all the slots.
+	 */
+	if (remote_slot_list)
+		wait_for_standby_confirmation(max_confirmed_lsn, wrconn);
+
 	/* Now sync the slots locally */
 	foreach(cell, remote_slot_list)
 	{
@@ -971,7 +1034,7 @@ validate_slotsync_parameters(char **dbname)
  * if validaity fails.
  */
 static void
-slotsync_reread_config()
+slotsync_reread_config(List **standby_slots)
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 	char	   *slotname = pstrdup(PrimarySlotName);
@@ -983,8 +1046,14 @@ slotsync_reread_config()
 	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
 	Assert(dbname);
 
-	ConfigReloadPending = false;
-	ProcessConfigFile(PGC_SIGHUP);
+	/*
+	 * Reload configs and recreate the standby_slot_names_list if GUC
+	 * standby_slot_names changed.
+	 */
+	if (standby_slots)
+		WalSndRereadConfigAndReInitSlotList(standby_slots);
+	else
+		ProcessConfigFile(PGC_SIGHUP);
 
 	if ((strcmp(conninfo, PrimaryConnInfo) != 0) ||
 		(strcmp(slotname, PrimarySlotName) != 0) ||
@@ -1021,7 +1090,8 @@ slotsync_reread_config()
  * Interrupt handler for main loop of slot sync worker.
  */
 static void
-ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
+ProcessSlotSyncInterrupts(WalReceiverConn **wrconn,
+						  List **standby_slots)
 {
 	CHECK_FOR_INTERRUPTS();
 
@@ -1037,7 +1107,10 @@ ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
 
 
 	if (ConfigReloadPending)
-		slotsync_reread_config();
+	{
+		ConfigReloadPending = false;
+		slotsync_reread_config(standby_slots);
+	}
 }
 
 /*
@@ -1105,7 +1178,7 @@ ReplSlotSyncWorkerMain(Datum main_arg)
 		int			rc;
 		long		naptime;
 
-		ProcessSlotSyncInterrupts(&wrconn);
+		ProcessSlotSyncInterrupts(&wrconn, NULL /* standby_slots */ );
 
 		naptime = synchronize_slots(wrconn);
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 1ed711d26b..f943e82877 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1608,7 +1608,7 @@ PhysicalWakeupLogicalWalSnd(void)
  * Reload the config file and reinitialize the standby slot list if the GUC
  * standby_slot_names has changed.
  */
-static void
+void
 WalSndRereadConfigAndReInitSlotList(List **standby_slots)
 {
 	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
@@ -1633,7 +1633,7 @@ WalSndRereadConfigAndReInitSlotList(List **standby_slots)
  * it removes slots that have been invalidated, dropped, or converted to
  * logical slots.
  */
-static void
+void
 WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 {
 	ListCell   *lc;
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 1fcc22a127..c5c4714788 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -15,6 +15,7 @@
 #include <signal.h>
 
 #include "access/xlogdefs.h"
+#include "nodes/pg_list.h"
 
 /*
  * What to do with a snapshot in create replication slot command.
@@ -50,7 +51,11 @@ extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn,
+									 List **standby_slots);
 extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
+extern List *WalSndGetStandbySlots(void);
+extern void WalSndRereadConfigAndReInitSlotList(List **standby_slots);
 
 /*
  * Remember that we want to wakeup walsenders later
-- 
2.30.0.windows.2



  [application/octet-stream] v41-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (136.0K, ../../OS0PR01MB57163425CEE6CEC16C7D6FA59483A@OS0PR01MB5716.jpnprd01.prod.outlook.com/3-v41-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From 10d729cd12801859c90a6351e78b7fb8ce7ef19c Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Tue, 21 Nov 2023 14:50:21 +0530
Subject: [PATCH v41 1/3] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to
the physical standbys so that logical replication can be resumed after
failover. It is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API.

Ex1:
CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

Ex2: (failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

A new replication command called ALTER_REPLICATION_SLOT and a
corresponding walreceiver API function named walrcv_alter_slot have been
implemented. They allow subscribers or users to modify the failover
property of a replication slot on the publisher.

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).
---
 contrib/test_decoding/expected/slot.out       |  58 +++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |  19 +-
 doc/src/sgml/ref/create_subscription.sgml     |  23 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       | 100 +++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  38 +-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  53 ++-
 src/backend/replication/logical/worker.c      |  71 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 182 +++++++++-
 src/backend/replication/slotfuncs.c           |  19 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 335 ++++++++++++++++--
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_upgrade/info.c                     |   5 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 src/test/recovery/t/050_verify_slot_order.pl  | 149 ++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 +++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 48 files changed, 1352 insertions(+), 166 deletions(-)
 create mode 100644 src/test/recovery/t/050_verify_slot_order.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 94d1eb2b81..30d9b53e03 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4360,6 +4360,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..239c43ba2f 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
@@ -230,6 +233,16 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
+
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property of the new slot may differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription, you need to adjust the
+      <literal>failover</literal> property when creating the slot so that it
+      matches the value specified in subscription.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..fa6cd1c43f 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,29 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..3f35f42621 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,34 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If the slot_name is specified without the create_slot option, it
+			 * is possible that the user intends to use an existing slot on the
+			 * publisher, so here we alter the failover property of the slot to
+			 * match the failover value in subscription.
+			 *
+			 * We do not need to change the failover to false if the server
+			 * does not support failover (e.g. pre-PG17)
+			 */
+			else if (opts.slot_name &&
+					(failover_enabled || walrcv_server_version(wrconn) >= 170000))
+			{
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+								opts.slot_name,
+								failover_enabled ? "true" : "false")));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,13 +1331,21 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
@@ -1334,8 +1394,8 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
@@ -1347,6 +1407,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
 
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when failover is enabled"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
 
 					/* Refresh the new list of publications. */
@@ -1392,6 +1462,16 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when failover is enabled"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
 
 				AlterSubscription_refresh(sub, opts.copy_data, NULL);
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 60d5c1fc40..7fa0bb8a3a 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -883,8 +887,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -913,7 +917,8 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+			appendStringInfoString(&cmd, "FAILOVER, ");
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -982,6 +987,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					 bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index df3c42eb5d..d2d8bf1a7a 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -614,15 +614,28 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "two_phase")));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "failover")));
+
 				should_exit = true;
 			}
 		}
@@ -1420,7 +1433,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1722,10 +1736,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1733,9 +1749,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1749,9 +1764,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..cf5d9caa8b 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,6 +132,37 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * failover = true when creating the subscription. Enabling failover allows us
+ * to smoothly transition to the promoted standby, ensuring that we can
+ * subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby. During syncing, the local restart_lsn and/or local catalog_xmin of
+ * the newly created slot on the standby are typically ahead of those on the
+ * primary. Therefore, the standby needs to wait for the primary server's
+ * restart_lsn and catalog_xmin to catch up, which takes time.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
@@ -3947,6 +3978,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4514,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4572,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4612,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..d35e6dec55 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List	   *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -251,7 +263,8 @@ ReplicationSlotValidateName(const char *name, int elevel)
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +324,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +693,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2198,140 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+
+	/* Need a modifiable copy of string */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into list of identifiers */
+	if (!SplitIdentifierString(rawname, ',', &elemlist))
+	{
+		/* syntax error in name list */
+		GUC_check_errdetail("List syntax is invalid.");
+		goto ret_standby_slot_names_ng;
+	}
+
+	/*
+	 * Verify 'type' of slot now.
+	 *
+	 * Skip check if replication slots' data is not initialized yet i.e. we
+	 * are in startup process.
+	 */
+	if (!ReplicationSlotCtl)
+		goto ret_standby_slot_names_ok;
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+			goto ret_standby_slot_names_ng;
+
+		if (!SlotIsPhysical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			goto ret_standby_slot_names_ng;
+		}
+	}
+
+ret_standby_slot_names_ok:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return true;
+
+ret_standby_slot_names_ng:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return false;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..fb6e37d2c3 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -679,6 +689,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +745,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +785,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 2398167f49..e27d231174 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -386,7 +386,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..f9000b3ef8 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool	failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,237 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up logical walsenders with failover-enabled slots if the physical slot
+ * of the current walsender is specified in standby_slot_names GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn. Additionally,
+ * it removes slots that have been invalidated, dropped, or converted to
+ * logical slots.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
+		bool		invalidated = false;
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (slot && SlotIsPhysical(slot))
+		{
+			SpinLockAcquire(&slot->mutex);
+			restart_lsn = slot->data.restart_lsn;
+			invalidated = slot->data.invalidated != RS_INVAL_NONE;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Continue if the current slot hasn't caught up. */
+		if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
+			restart_lsn < wait_for_lsn)
+		{
+			/* Log warning if no active_pid for this physical slot */
+			if (slot->active_pid == 0)
+				ereport(WARNING,
+						errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+							   name, "standby_slot_names"),
+						errdetail("Logical replication is waiting on the "
+								  "standby associated with \"%s\"", name),
+						errhint("Consider starting standby associated with "
+								"\"%s\" or amend standby_slot_names", name));
+
+			continue;
+		}
+
+		/*
+		 * It may happen that the slot specified in standby_slot_names GUC
+		 * value is dropped, so let's skip over it.
+		 */
+		else if (!slot)
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+
+		/*
+		 * If a logical slot name is provided in standby_slot_names, issue a
+		 * WARNING and skip it. Although logical slots are disallowed in the
+		 * GUC check_hook(validate_standby_slots), it is still possible for a
+		 * user to drop an existing physical slot and recreate a logical slot
+		 * with the same name. Since it is harmless, a WARNING should be
+		 * enough, no need to error-out.
+		 */
+		else if (SlotIsLogical(slot))
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+
+		/*
+		 * Specified physical slot may have been invalidated, so no point in
+		 * waiting for it.
+		 */
+		else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
+			warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+		else
+			Assert(restart_lsn >= wait_for_lsn);
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving the given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, the
+ * function also waits for all the specified streaming replication standby
+ * servers to confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr if we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
-	if (RecentFlushPtr != InvalidXLogRecPtr &&
-		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	if (!XLogRecPtrIsInvalid(RecentFlushPtr) && loc <= RecentFlushPtr)
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know we
+		 * have enough WAL available and all the standby servers have confirmed
+		 * receipt of WAL up to RecentFlushPtr. This is particularly interesting
+		 * if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1829,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1844,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,8 +1883,14 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
 			break;
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
@@ -1654,9 +1931,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2098,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2335,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3598,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3669,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another CV
+	 * that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5c6f5af873..f12e2e384e 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4572,6 +4572,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..5d940b72cd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -329,6 +329,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..e16286f18c 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,6 +679,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
@@ -687,6 +688,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +699,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..f6ac78418e 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced
+								 * to the physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 5b01cf8c40..0a1c467ed0 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -158,7 +158,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -172,8 +172,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 049801186c..905964a2e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3327,7 +3327,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb58dee3bc..d906734750 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..ca06e5b1ad 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)?
+	 * Only relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 04b439dc50..61bc8de72c 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -356,9 +356,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									   const char *slotname,
+									   bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -400,6 +411,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -429,8 +441,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 268f8e8d0f..1fcc22a127 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -14,6 +14,8 @@
 
 #include <signal.h>
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -47,6 +49,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 47854b5cd4..4378690ab0 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -258,7 +258,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..3be3ee52fc 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_verify_slot_order.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
new file mode 100644
index 0000000000..bff0f52a46
--- /dev/null
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -0,0 +1,149 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres', "CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+$subscriber1->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true
+$subscriber1->safe_psql('postgres',
+		"CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);");
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+$subscriber2->safe_psql('postgres',
+		"CREATE SUBSCRIPTION mysub2 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);");
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby comes
+# up.
+$standby1->stop;
+
+# Create some data on primary
+my $primary_row_count = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = 0 FROM tab_int;");
+is($result, 't', "subscriber1 doesn't get data from primary until standby1 acknowledges changes");
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 86a9886d4f..d80d30e99c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3862,6 +3863,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.30.0.windows.2



  [application/octet-stream] v41-0002-Add-logical-slot-sync-capability-to-the-physical.patch (79.4K, ../../OS0PR01MB57163425CEE6CEC16C7D6FA59483A@OS0PR01MB5716.jpnprd01.prod.outlook.com/4-v41-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From 478d1fbdf1d9ba442e6ce8916024c7626f435d23 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Mon, 27 Nov 2023 16:47:02 +0800
Subject: [PATCH v41 2/3] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover. All the failover logical
replication slots on the primary (assuming configurations are
appropriate) are automatically created on the physical standbys and
are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The nap time of worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. And if
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated. If a logical slot on the primary is valid but is invalidated
on the standby due to conflict (say required rows removed on the primary),
then that slot is dropped and recreated on the standby in next sync-cycle.
It is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently).

Slots synced on the standby can be identified using 'sync_state' column of
pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but waiting for the remote slot on the
     primary server to catch up.
'r': ready for periodic syncs.
---
 doc/src/sgml/bgworker.sgml                    |   16 +-
 doc/src/sgml/config.sgml                      |   35 +-
 doc/src/sgml/logicaldecoding.sgml             |   13 +
 doc/src/sgml/system-views.sgml                |   23 +
 src/backend/access/transam/xlogrecovery.c     |   10 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    4 +
 src/backend/postmaster/postmaster.c           |    6 +
 .../libpqwalreceiver/libpqwalreceiver.c       |   44 +-
 src/backend/replication/logical/Makefile      |    1 +
 src/backend/replication/logical/logical.c     |   20 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1234 +++++++++++++++++
 src/backend/replication/logical/tablesync.c   |    1 +
 src/backend/replication/slot.c                |   22 +-
 src/backend/replication/slotfuncs.c           |   37 +-
 src/backend/replication/walsender.c           |    6 +-
 src/backend/storage/ipc/ipci.c                |    2 +
 src/backend/tcop/postgres.c                   |   12 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   10 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/commands/subscriptioncmds.h       |    4 +
 src/include/postmaster/bgworker.h             |    1 +
 src/include/replication/slot.h                |   22 +-
 src/include/replication/walreceiver.h         |   19 +
 src/include/replication/worker_internal.h     |   12 +
 src/test/recovery/t/050_verify_slot_order.pl  |  127 ++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    2 +
 32 files changed, 1678 insertions(+), 30 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2c393385a9..377f301b63 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -121,11 +121,17 @@ typedef struct BackgroundWorker
    <literal>BgWorkerStart_ConsistentState</literal> (start as soon as a consistent state
    has been reached in a hot standby, allowing processes to connect to
    databases and run read-only queries), and
-   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system has
-   entered normal read-write state).  Note the last two values are equivalent
-   in a server that's not a hot standby.  Note that this setting only indicates
-   when the processes are to be started; they do not stop when a different state
-   is reached.
+   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system
+   has entered normal read-write state. Note that the
+   <literal>BgWorkerStart_ConsistentState</literal> and
+   <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
+   in a server that's not a hot standby), and
+   <literal>BgWorkerStart_ConsistentState_HotStandby</literal> (same meaning as
+   <literal>BgWorkerStart_ConsistentState</literal> but it is more strict in
+   terms of the server i.e. start the  worker only if it is hot-standby; if it is
+   consistent state in non-standby, worker will not be started). Note that this
+   setting only indicates when the processes are to be started; they do not stop
+   when a different state is reached.
   </para>
 
   <para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 30d9b53e03..1977962caf 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4373,6 +4373,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must enable
+        <varname>enable_syncslot</varname> for the standbys to receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4566,10 +4572,15 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           A password needs to be provided too, if the sender demands password
           authentication.  It can be provided in the
           <varname>primary_conninfo</varname> string, or in a separate
-          <filename>~/.pgpass</filename> file on the standby server (use
+          <filename>~/.pgpass</filename> file on the standby server. (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          Specify <literal>dbname</literal> in
+          <varname>primary_conninfo</varname> string to allow synchronization
+          of slots from the primary server to the standby server.
+          This will only be used for slot synchronization. It is ignored
+          for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4894,6 +4905,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..90bec06f36 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,19 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and set
+     <varname>enable_syncslot</varname> on the standby. The physical replication
+     slot for the standby should be listed in
+     <varname>standby_slot_names</varname> on the primary to prevent the
+     subscriber from consuming changes faster than the hot standby.
+     Additionally, similar to creating a logical replication slot on the hot
+     standby, <varname>hot_standby_feedback</varname> should be set on the
+     standby and a physical slot between the primary and the standby should be
+     used.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..f08beb83e1 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,29 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has enabled slots synchronization.
+      </para>
+      <para>
+       State code:
+       <literal>n</literal> = none for user created slots,
+       <literal>i</literal> = sync initiated for the slot but slot is not ready
+        yet for periodic syncs,
+       <literal>r</literal> = ready for periodic syncs
+      </para>
+      <para>
+      The primary server will have sync_state as 'n' for all the slots.
+      But if the standby is promoted to become the new primary server,
+      sync_state can be seen 'r' as well. On this new primary server, slots
+      with sync_state as 'r' and 'n' behaves the same.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c61566666a..9fb09ba0fc 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1436,15 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 48a9924527..c65a678c95 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -22,6 +22,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "replication/worker_internal.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -130,6 +131,9 @@ static const struct
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 7a5cd06c5c..6915424b01 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -117,6 +117,7 @@
 #include "postmaster/syslogger.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -1001,6 +1002,8 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	SlotSyncWorkerRegister();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
@@ -5781,6 +5784,9 @@ bgworker_should_start_now(BgWorkerStartTime start_time)
 		case PM_HOT_STANDBY:
 			if (start_time == BgWorkerStart_ConsistentState)
 				return true;
+			else if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
+					 pmState != PM_RUN)
+				return true;
 			/* fall through */
 
 		case PM_RECOVERY:
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 7fa0bb8a3a..10c6e180c0 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -413,6 +416,45 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	PQconninfoOption *opt;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
@@ -992,7 +1034,7 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
  */
 static void
 libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
-					 bool failover)
+					bool failover)
 {
 	StringInfoData cmd;
 	PGresult   *res;
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..7de68a61e2 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,26 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	/*
+	 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+	 * promotion.
+	 */
+	if (!RecoveryInProgress() && slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+		elog(ERROR, "replication slot \"%s\" was not synced completely from the primary server",
+			 NameStr(slot->data.name));
+
+	/*
+	 * Do not allow consumption of a "synchronized" slot until the standby
+	 * gets promoted.
+	 */
+	if (RecoveryInProgress() && slot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot use replication slot \"%s\" for logical decoding",
+						NameStr(slot->data.name)),
+				 errdetail("This slot is being synced from the primary server."),
+				 errhint("Specify another replication slot.")));
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..ea1e333c70
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1234 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * It also takes care of dropping the slots which were created by it and are
+ * currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/table.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_database.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/*
+ * Struct for sharing information between startup process and slot
+ * sync worker.
+ *
+ * Slot sync worker's pid is needed by startup process in order to
+ * shut it down during promotion.
+ */
+typedef struct SlotSyncWorkerCtx
+{
+	pid_t		pid;
+	slock_t		mutex;
+} SlotSyncWorkerCtx;
+
+SlotSyncWorkerCtx *SlotSyncWorker = NULL;
+
+/* GUC variable */
+bool		enable_syncslot = false;
+
+/* The last sync-cycle time when the worker updated any of the slots. */
+static TimestampTz last_update_time;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+/*
+ * Number of attempts for wait_for_primary_slot_catchup() after
+ * which it aborts the wait and the slot sync worker then moves
+ * to the next slot creation/sync.
+ */
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
+
+static void ProcessSlotSyncInterrupts(WalReceiverConn **wrconn);
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * *persist will be set to false if the slot has disappeared or was invalidated
+ * on the primary; otherwise, it will be set to true.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *persist)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+	if (persist)
+		*persist = true;
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn, confirmed_flush_lsn,"
+					 " catalog_xmin FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		XLogRecPtr	new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *slot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Handle any termination request if any */
+		ProcessSlotSyncInterrupts(&wrconn);
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot info for slot \"%s\" from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" disappeared from the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(slot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" invalidated on the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			ExecClearTuple(slot);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * Once we got valid restart_lsn, then confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(slot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\" and moving"
+						   " to the next slot, will attempt creating it again",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up.
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			slots = lappend(slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(slots);
+}
+
+/*
+ * Get list of local logical slot names which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slot_names(void)
+{
+	List	   *localSyncedSlots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			localSyncedSlots = lappend(localSyncedSlots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return localSyncedSlots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+check_sync_slot_validity(ReplicationSlot *local_slot, List *remote_slots,
+						 bool *locally_invalidated)
+{
+	ListCell   *cell;
+
+	foreach(cell, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Also drop the slots that are valid on the primary that got invalidated
+ * on the standby due to conflict (say required rows removed on the primary).
+ * The assumption is, that these will get recreated in next sync-cycle and
+ * it is okay to drop and recreate such slots as long as these are not
+ * consumable on the standby (which is the case currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slot_list = NIL;
+	ListCell   *lc_slot;
+
+	/*
+	 * Get the list of local 'synced' slot so that those not on remote could
+	 * be dropped.
+	 */
+	local_slot_list = get_local_synced_slot_names();
+
+	foreach(lc_slot, local_slot_list)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc_slot);
+		bool		local_exists = false;
+		bool		locally_invalidated = false;
+
+		local_exists = check_sync_slot_validity(local_slot, remote_slot_list,
+												&locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!local_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Constructs the query in order to get failover logical slots
+ * information from the primary server.
+ */
+static void
+construct_slot_query(StringInfo s)
+{
+	/*
+	 * Fetch slots with failover enabled.
+	 *
+	 * If we are on cascading standby, we should fetch only those slots from
+	 * the first standby which have sync_state as either 'n' or 'r'. Slots
+	 * with sync_state as 'i' are not sync ready yet. And when we are on the
+	 * first standby, the primary server is supposed to have slots with
+	 * sync_state as 'n' only (or it may have all 'n', 'r' and 'i' if standby
+	 * is promoted as primary). Thus in all the cases, filter sync_state !='i'
+	 * is appropriate one.
+	 */
+	appendStringInfo(s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover and sync_state != 'i'");
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ */
+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+					 bool *slot_updated)
+{
+	ReplicationSlot *s;
+	char		sync_state = 0;
+
+	/*
+	 * Make sure that concerned WAL is received before syncing slot to target
+	 * lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+
+		return;
+	}
+
+	/* Search for the named slot */
+	if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		SpinLockAcquire(&s->mutex);
+		sync_state = s->data.sync_state;
+		SpinLockRelease(&s->mutex);
+	}
+
+	StartTransactionCommand();
+
+	/*
+	 * Already existing slot (created by slot sync worker) and ready for sync,
+	 * acquire and sync it.
+	 */
+	if (sync_state == SYNCSLOT_STATE_READY)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&MyReplicationSlot->mutex);
+			MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&MyReplicationSlot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * With hot_standby_feedback enabled and invalidations handled
+		 * apropriately as above, this should never happen.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn)
+		{
+			ereport(ERROR,
+					errmsg("not synchronizing local slot \"%s\" LSN(%X/%X)"
+						   " to remote slot's LSN(%X/%X) as synchronization "
+						   " would move it backwards", remote_slot->name,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   LSN_FORMAT_ARGS(remote_slot->restart_lsn)));
+
+			goto cleanup;
+		}
+
+		if (remote_slot->confirmed_lsn != MyReplicationSlot->data.confirmed_flush ||
+			remote_slot->restart_lsn != MyReplicationSlot->data.restart_lsn ||
+			remote_slot->catalog_xmin != MyReplicationSlot->data.catalog_xmin)
+		{
+			/* Update LSN of slot to remote slot's current position */
+			local_slot_update(remote_slot);
+			ReplicationSlotSave();
+			*slot_updated = true;
+		}
+	}
+
+	/*
+	 * Already existing slot but not ready (i.e. waiting for the primary
+	 * server to catch-up), lets attempt to make it sync-ready now.
+	 */
+	else if (sync_state == SYNCSLOT_STATE_INITIATED)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * Refer the slot creation part (last 'else' block) for more details
+		 * on this wait.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+			{
+				goto cleanup;
+			}
+		}
+
+		/*
+		 * Wait for primary is over, update the lsns and mark the slot as
+		 * READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Save the changes */
+		ReplicationSlotMarkDirty();
+		ReplicationSlotSave();
+
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready now",
+							remote_slot->name));
+	}
+	/* User created slot with the same name exists, raise ERROR. */
+	else if (sync_state == SYNCSLOT_STATE_NONE)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("skipping sync of slot \"%s\" as it is a user created"
+						" slot", remote_slot->name),
+				 errdetail("This slot has failover enabled on the primary and"
+						   " thus is sync candidate but user created slot with"
+						   " the same name already exists on the standby")));
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+		ReplicationSlot *slot;
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover,
+							  SYNCSLOT_STATE_INITIATED);
+
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * If the local restart_lsn and/or local catalog_xmin is ahead of
+		 * those on the remote then we cannot create the local slot in sync
+		 * with the primary server because that would mean moving the local
+		 * slot backwards and we might not have WALs retained for old LSN. In
+		 * this case we will wait for the primary server's restart_lsn and
+		 * catalog_xmin to catch up with the local one before attempting the
+		 * sync.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			bool		persist;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &persist))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid) and attempt the wait and synchronization in
+				 * the next cycle.
+				 */
+				if (persist)
+				{
+					ReplicationSlotPersist();
+					*slot_updated = true;
+				}
+
+				goto cleanup;
+			}
+		}
+
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready now",
+							remote_slot->name));
+	}
+
+cleanup:
+
+	ReplicationSlotRelease();
+	CommitTransactionCommand();
+
+	return;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and update
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns nap time for the next sync-cycle.
+ */
+static long
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *slot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	MemoryContext oldctx = CurrentMemoryContext;
+	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	ListCell   *cell;
+	bool		slot_updated = false;
+	TimestampTz now;
+
+	/* The primary_slot_name is not set yet or WALs not received yet */
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+		return naptime;
+
+	/* The syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	/*
+	 * Make result tuples live outside TopTransactionContext to make them
+	 * accessible even after transaction is committed.
+	 */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct query to get slots info from the primary server */
+	initStringInfo(&s);
+	construct_slot_query(&s);
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(slot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+																	   5, &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(slot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(slot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(slot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(slot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(slot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(cell, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		synchronize_one_slot(wrconn, remote_slot, &slot_updated);
+	}
+
+	now = GetCurrentTimestamp();
+
+	/*
+	 * If any of the slots get updated in this sync-cycle, retain default
+	 * naptime and update 'last_update_time' in slot sync worker. But if no
+	 * activity is observed in this sync-cycle, then increase naptime provided
+	 * inactivity time reaches threshold.
+	 */
+	if (slot_updated)
+		last_update_time = now;
+
+	else if (TimestampDifferenceExceeds(last_update_time,
+										now, WORKER_INACTIVITY_THRESHOLD_MS))
+		naptime = WORKER_INACTIVITY_NAPTIME_MS;
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	return naptime;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker", &err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Checks if GUCs are set appropriately before starting slot sync worker
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+	/*
+	 * The slot sync feature itself is disabled, exit.
+	 */
+	if (!enable_syncslot)
+	{
+		ereport(LOG,
+				errmsg("exiting slot sync worker as enable_syncslot is disabled."));
+		proc_exit(0);
+	}
+
+	/*
+	 * Since 'enable_syncslot' is on, check that other GUC settings
+	 * (primary_slot_name, hot_standby_feedback, wal_level, primary_conninfo)
+	 * are compatible with slot synchronization. If not, raise ERROR.
+	 */
+
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as primary_slot_name is not set"));
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as hot_standby_feedback is off"));
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+				errmsg("exiting slots synchronisation as it requires wal_level >= logical"));
+
+	/*
+	 * The primary_conninfo is required to make connection to primary for
+	 * getting slots information.
+	 */
+	if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as primary_conninfo is not set"));
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	*dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (*dbname == NULL)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as dbname is not specified in primary_conninfo"));
+
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs changed, validate the values again
+ * through validate_slotsync_parameters() which will exit the worker
+ * if validaity fails.
+ */
+static void
+slotsync_reread_config()
+{
+	char	   *conninfo = pstrdup(PrimaryConnInfo);
+	char	   *slotname = pstrdup(PrimarySlotName);
+	bool		syncslot = enable_syncslot;
+	bool		standbyfeedback = hot_standby_feedback;
+	bool		revalidate = false;
+	char	   *dbname;
+
+	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	Assert(dbname);
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if ((strcmp(conninfo, PrimaryConnInfo) != 0) ||
+		(strcmp(slotname, PrimarySlotName) != 0) ||
+		(syncslot != enable_syncslot) ||
+		(standbyfeedback != hot_standby_feedback))
+	{
+		revalidate = true;
+	}
+
+	pfree(conninfo);
+	pfree(slotname);
+
+	if (revalidate)
+	{
+		char	   *new_dbname;
+
+		validate_slotsync_parameters(&new_dbname);
+
+		/*
+		 * Since we have initialized this worker with old dbname, thus exit if
+		 * dbname changed. Let it get restarted and connect to new dbname
+		 * specified.
+		 */
+		if (strcmp(dbname, new_dbname) != 0)
+		{
+			ereport(ERROR,
+					errmsg("exiting slot sync woker as dbname in primary_conninfo changed"));
+		}
+	}
+}
+
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting"
+					   " down on receiving SIGINT"));
+
+		walrcv_disconnect(*wrconn);
+		proc_exit(0);
+	}
+
+
+	if (ConfigReloadPending)
+		slotsync_reread_config();
+}
+
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = 0;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	Assert(SlotSyncWorker->pid == 0);
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	validate_slotsync_parameters(&dbname);
+
+	/*
+	 * Connect to the database specified by user in PrimaryConnInfo. We need a
+	 * database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime;
+
+		ProcessSlotSyncInterrupts(&wrconn);
+
+		naptime = synchronize_slots(wrconn);
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsSlotSyncWorker(void)
+{
+	return SlotSyncWorker->pid == MyProcPid;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (!SlotSyncWorker->pid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Wait for it to die. */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long. */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (!SlotSyncWorker->pid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Allocate and initialize slow sync worker shared memory
+ */
+void
+SlotSyncWorkerShmemInit(void)
+{
+	Size		size;
+	bool		found;
+
+	size = sizeof(SlotSyncWorkerCtx);
+	size = MAXALIGN(size);
+
+	SlotSyncWorker = (SlotSyncWorkerCtx *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
+
+	if (!found)
+	{
+		memset(SlotSyncWorker, 0, size);
+		SpinLockInit(&SlotSyncWorker->mutex);
+	}
+}
+
+/*
+ * Register the background worker for slots synchronization.
+ */
+void
+SlotSyncWorkerRegister(void)
+{
+	BackgroundWorker bgw;
+
+	memset(&bgw, 0, sizeof(bgw));
+
+	/* We need database connection which needs shared-memory access as well. */
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+
+	/* Start as soon as a consistent state has been reached in a hot standby */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState_HotStandby;
+
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "replication slot sync worker");
+	snprintf(bgw.bgw_type, BGW_MAXLEN,
+			 "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
+	bgw.bgw_notify_pid = 0;
+	bgw.bgw_main_arg = (Datum) 0;
+
+	RegisterBackgroundWorker(&bgw);
+}
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index d2d8bf1a7a..f1675dbd85 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "commands/subscriptioncmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index d35e6dec55..eac8fe2fe8 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -112,7 +113,7 @@ int			max_replication_slots = 10; /* the maximum number of replication
 char	   *standby_slot_names;
 
 /* This is parsed and cached list for raw standby_slot_names. */
-static List	   *standby_slot_names_list = NIL;
+static List *standby_slot_names_list = NIL;
 
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
@@ -264,7 +265,7 @@ ReplicationSlotValidateName(const char *name, int elevel)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover)
+					  bool two_phase, bool failover, char sync_state)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -325,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = sync_state;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -684,12 +686,26 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+	{
+		ReplicationSlotRelease();
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary.")));
+	}
+
 	ReplicationSlotDropAcquired();
 }
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index fb6e37d2c3..79b9ca5704 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -44,7 +44,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false);
+						  false, SYNCSLOT_STATE_NONE);
 
 	if (immediately_reserve)
 	{
@@ -137,7 +137,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover);
+						  failover, SYNCSLOT_STATE_NONE);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index f9000b3ef8..1ed711d26b 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1071,7 +1071,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false);
+							  false, false, SYNCSLOT_STATE_NONE);
 
 		if (reserve_wal)
 		{
@@ -1102,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover);
+							  two_phase, failover, SYNCSLOT_STATE_NONE);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, false);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index a3d8eacb8d..44668d8efd 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -36,6 +36,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -293,6 +294,7 @@ CreateSharedMemoryAndSemaphores(void)
 	WalRcvShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index e415cf1f34..2bf47c494b 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -60,6 +60,7 @@
 #include "replication/logicalworker.h"
 #include "replication/slot.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
@@ -3286,6 +3287,17 @@ ProcessInterrupts(void)
 			 */
 			proc_exit(1);
 		}
+		else if (IsSlotSyncWorker())
+		{
+			ereport(DEBUG1,
+					(errmsg_internal("replication slot sync worker is shutting down due to administrator command")));
+
+			/*
+			 * Slot sync worker can be stopped at any time.
+			 * Use exit status 1 so the background worker is restarted.
+			 */
+			proc_exit(1);
+		}
 		else if (IsBackgroundWorker)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f12e2e384e..8db16d3ecc 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2021,6 +2022,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_POSTMASTER, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5d940b72cd..0d400bf473 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -358,6 +358,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = on			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d906734750..6a8192ad83 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 214dc6c29e..75b4b2040d 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -17,6 +17,7 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"
 
 extern ObjectAddress CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										bool isTopLevel);
@@ -28,4 +29,7 @@ extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
 extern char defGetStreamingMode(DefElem *def);
 
+extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn,
+										char *slotname, bool missing_ok);
+
 #endif							/* SUBSCRIPTIONCMDS_H */
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index e90ff376a6..8559900b70 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -79,6 +79,7 @@ typedef enum
 	BgWorkerStart_PostmasterStart,
 	BgWorkerStart_ConsistentState,
 	BgWorkerStart_RecoveryFinished,
+	BgWorkerStart_ConsistentState_HotStandby,
 } BgWorkerStartTime;
 
 #define BGW_DEFAULT_RESTART_INTERVAL	60
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index ca06e5b1ad..3cfcfef638 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -15,7 +15,6 @@
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "storage/spin.h"
-#include "replication/walreceiver.h"
 
 /*
  * Behaviour of replication slots, upon release or crash.
@@ -52,6 +51,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +119,13 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Is this a slot created by a sync-slot worker?
+	 *
+	 * Relevant for logical slots on the physical standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)?
 	 * Only relevant for logical slots on the primary server.
@@ -225,9 +239,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover);
+								  bool two_phase, bool failover,
+								  char sync_state);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
@@ -253,7 +268,6 @@ extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_l
 extern int	ReplicationSlotIndex(ReplicationSlot *slot);
 extern bool ReplicationSlotName(int index, Name name);
 extern void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot);
-extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok);
 
 extern void StartupReplicationSlots(void);
 extern void CheckPointReplicationSlots(bool is_shutdown);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 61bc8de72c..2f7ae9dc69 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -20,6 +20,7 @@
 #include "pgtime.h"
 #include "port/atomics.h"
 #include "replication/logicalproto.h"
+#include "replication/slot.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/latch.h"
@@ -280,6 +281,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -404,6 +420,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -429,6 +446,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 4378690ab0..3379b1ffbf 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -239,6 +239,11 @@ extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
+/* Slot sync worker objects */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+extern PGDLLIMPORT bool enable_syncslot;
+
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
@@ -328,6 +333,13 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
+extern void SlotSyncWorkerRegister(void);
+extern void ShutDownSlotSync(void);
+extern void slotsync_drop_initiated_slots(void);
+extern bool IsSlotSyncWorker(void);
+extern void SlotSyncWorkerShmemInit(void);
+
 #define isParallelApplyWorker(worker) ((worker)->in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
 #define isTablesyncWorker(worker) ((worker)->in_use && \
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
index bff0f52a46..e7a00bde12 100644
--- a/src/test/recovery/t/050_verify_slot_order.pl
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -146,4 +146,131 @@ $result = $subscriber1->safe_psql('postgres',
 	"SELECT count(*) = $primary_row_count FROM tab_int;");
 is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
 
+# Test logical failover slots on the standby
+# Configure standby3 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub1_slot->| ----> subscriber1 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb3_slot--->| ----> standby3 (connected via streaming replication)
+#                                        |                 lsub1_slot(synced_slot)
+
+# Cleanup old standby_slot_names
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = ''
+));
+$primary->start;
+
+$primary->psql('postgres',
+		q{SELECT pg_create_physical_replication_slot('sb3_slot');});
+
+$backup_name = 'backup2';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+my $connstr_1 = $primary->connstr;
+$standby3->stop;
+$standby3->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb3_slot'
+});
+$standby3->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+$standby3->start;
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb3_slot'
+));
+$primary->start;
+
+# Restart the standby
+$standby3->restart;
+
+# Wait for the standby to start sync
+my $offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots;}
+  ),
+  'lsub1_slot',
+  'failover slot was created');
+
+# Verify slot properties on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|r",
+  'logical slot has sync_state as ready and failover as true on standby');
+
+# Verify slot properties on the primary
+is( $primary->safe_psql('postgres',
+    q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|n",
+  'logical slot has sync_state as none and failover as true on primary');
+
+# Test to confirm that restart_lsn of the logical slot on the primary is synced to the standby
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_int;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# let the slots get synced on the standby
+sleep 2;
+
+# Get the restart_lsn for the logical slot lsub1_slot on the primary
+my $primary_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that restart_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'restart_lsn of slot lsub1_slot synced to standby');
+
+# Get the confirmed_flush_lsn for the logical slot lsub1_slot on the primary
+$primary_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that confirmed_flush_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub1_slot synced to the standby');
+
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d80d30e99c..fb84b9509a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2314,6 +2314,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2572,6 +2573,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorkerCtx
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.30.0.windows.2



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 06:49               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-28 09:40                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 15:58                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 09:11                     ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-11-30 07:05                       ` Peter Smith <[email protected]>
  1 sibling, 0 replies; 113+ messages in thread

From: Peter Smith @ 2023-11-30 07:05 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; shveta malik <[email protected]>; Amit Kapila <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Here are some review comments for v41-0001.

======
doc/src/sgml/ref/alter_subscription.sgml

1.
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property of the new slot may
differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription, you need to adjust the
+      <literal>failover</literal> property when creating the slot so that it
+      matches the value specified in subscription.
+     </para>

For the second part a) it should be a separate sentence, and b) IMO
you are not really "adjusting" something if you are "creating" it.

SUGGESTION
When altering the slot_name, the failover property of the new slot may
differ from the failover parameter specified in the subscription. When
creating the slot  ensure the slot failover property matches the
failover parameter value of the subscription.

======
src/backend/catalog/pg_subscription.c

2. AlterSubscription

+ if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed
when failover is enabled"),

This is another example where the "two_phase" and "failover" should be
extracted to make a common message for the translator.

(Already posted this comment before -- see [1] #13)

~~~

3.
+ /*
+ * See comments above for twophasestate, same holds true for
+ * 'failover'
+ */
+ if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed
when failover is enabled"),
+ errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false,
or use DROP/CREATE SUBSCRIPTION.")));

This is another example where the "two_phase" and "failover" should be
extracted to make a common message for the translator.

(Already posted this comment before -- see [1] #14)

======
src/backend/replication/walsender.c

4.
+/*
+ * Wake up logical walsenders with failover-enabled slots if the physical slot
+ * of the current walsender is specified in standby_slot_names GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)

Is it better to refer to "walsender processes" being woken instead of
just walsenders?

e.g.
"Wake up logical walsenders..." --> "Wake up the logical walsender processes..."

======
[1] v35-0001 review.
https://www.postgresql.org/message-id/CAHut%2BPv-yu71ogj_hRi6cCtmD55bsyw7XTxj1Nq8yVFKpY3NDQ%40mail.g...

Kind Regards,
Peter Smith.
Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 06:49               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-28 09:40                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 15:58                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 09:11                     ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-12-01 04:10                       ` Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-01 04:51                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-12-01 04:10 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Drouvot, Bertrand <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Wednesday, November 29, 2023 5:12 PM Zhijie Hou (Fujitsu) <[email protected]> wrote:

I was reviewing slotsync worker design and here
are few comments on 0002 patch:

1.

+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))

I think we'd better take spinlock when accessing these shared memory fields.

2.

	/*
	 * The slot sync feature itself is disabled, exit.
	 */
	if (!enable_syncslot)
	{
		ereport(LOG,
				errmsg("exiting slot sync worker as enable_syncslot is disabled."));

Can we check the GUC when registering the worker(SlotSyncWorkerRegister),
so that the worker won't be started if enable_syncslot is false.

3. In synchronize_one_slot, do we need to skip the slot sync and drop if the
local slot is a physical one ?

4.

			*locally_invalidated =
				(remote_slot->invalidated == RS_INVAL_NONE) &&
				(local_slot->data.invalidated != RS_INVAL_NONE);

When reading the invalidated flag of local slot, I think we'd better take
spinlock.

Best Regards,
Hou zj


^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 06:49               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-28 09:40                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 15:58                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 09:11                     ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-01 04:10                       ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-12-01 04:51                         ` shveta malik <[email protected]>
  2023-12-01 05:47                           ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-01 04:51 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; +Cc: Amit Kapila <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Drouvot, Bertrand <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Fri, Dec 1, 2023 at 9:40 AM Zhijie Hou (Fujitsu)
<[email protected]> wrote:
>
> On Wednesday, November 29, 2023 5:12 PM Zhijie Hou (Fujitsu) <[email protected]> wrote:
>
> I was reviewing slotsync worker design and here
> are few comments on 0002 patch:

Thanks for reviewing the patch.

>
> 1.
>
> +       if (!WalRcv ||
> +               (WalRcv->slotname[0] == '\0') ||
> +               XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
>
> I think we'd better take spinlock when accessing these shared memory fields.
>
> 2.
>
>         /*
>          * The slot sync feature itself is disabled, exit.
>          */
>         if (!enable_syncslot)
>         {
>                 ereport(LOG,
>                                 errmsg("exiting slot sync worker as enable_syncslot is disabled."));
>
> Can we check the GUC when registering the worker(SlotSyncWorkerRegister),
> so that the worker won't be started if enable_syncslot is false.
>
> 3. In synchronize_one_slot, do we need to skip the slot sync and drop if the
> local slot is a physical one ?
>

IMO, if a local slot exists which is a physical one, it will be a user
created slot and in that case worker will error out on finding
existing slot with same name. And the case where local slot is
physical one but not user-created is not possible on standby (assuming
we have correct check on primary disallowing setting 'failover'
property for physical slot). Do you have some other scenario in mind,
which I am missing here?

> 4.
>
>                         *locally_invalidated =
>                                 (remote_slot->invalidated == RS_INVAL_NONE) &&
>                                 (local_slot->data.invalidated != RS_INVAL_NONE);
>
> When reading the invalidated flag of local slot, I think we'd better take
> spinlock.
>
> Best Regards,
> Hou zj






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 06:49               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-28 09:40                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 15:58                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 09:11                     ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-01 04:10                       ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-01 04:51                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-01 05:47                           ` Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-01 07:17                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-12-01 05:47 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Drouvot, Bertrand <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Friday, December 1, 2023 12:51 PM shveta malik <[email protected]> wrote:

Hi,

> 
> On Fri, Dec 1, 2023 at 9:40 AM Zhijie Hou (Fujitsu)
> <[email protected]> wrote:
> >
> > On Wednesday, November 29, 2023 5:12 PM Zhijie Hou (Fujitsu)
> <[email protected]> wrote:
> >
> > I was reviewing slotsync worker design and here
> > are few comments on 0002 patch:
> 
> Thanks for reviewing the patch.
> 
> >
> >
> > 3. In synchronize_one_slot, do we need to skip the slot sync and drop if the
> > local slot is a physical one ?
> >
> 
> IMO, if a local slot exists which is a physical one, it will be a user
> created slot and in that case worker will error out on finding
> existing slot with same name. And the case where local slot is
> physical one but not user-created is not possible on standby (assuming
> we have correct check on primary disallowing setting 'failover'
> property for physical slot). Do you have some other scenario in mind,
> which I am missing here?

I was thinking about the race condition when it has confirmed that the slot is
not a user created one and enter "sync_state == SYNCSLOT_STATE_READY" branch,
but at this moment, if someone uses "DROP_REPLICATION_SLOT" to drop this slot and
recreate another one(e.g. a physical one), then the slotsync worker will
overwrite the fields of this physical slot. Although this affects user created
logical slots in similar cases as well.

And the same is true for slotsync_drop_initiated_slots() and
drop_obsolete_slots(), as we don't lock the slots in the list, if user tri to
drop and re-create old slot concurrently, then we could drop user created slot
here.

Best Regards,
Hou zj


^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 06:49               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-28 09:40                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 15:58                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 09:11                     ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-01 04:10                       ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-01 04:51                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-01 05:47                           ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-12-01 07:17                             ` shveta malik <[email protected]>
  2023-12-01 09:33                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-01 07:17 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; +Cc: Amit Kapila <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Drouvot, Bertrand <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>; shveta malik <[email protected]>

On Fri, Dec 1, 2023 at 11:17 AM Zhijie Hou (Fujitsu)
<[email protected]> wrote:
>
> On Friday, December 1, 2023 12:51 PM shveta malik <[email protected]> wrote:
>
> Hi,
>
> >
> > On Fri, Dec 1, 2023 at 9:40 AM Zhijie Hou (Fujitsu)
> > <[email protected]> wrote:
> > >
> > > On Wednesday, November 29, 2023 5:12 PM Zhijie Hou (Fujitsu)
> > <[email protected]> wrote:
> > >
> > > I was reviewing slotsync worker design and here
> > > are few comments on 0002 patch:
> >
> > Thanks for reviewing the patch.
> >
> > >
> > >
> > > 3. In synchronize_one_slot, do we need to skip the slot sync and drop if the
> > > local slot is a physical one ?
> > >
> >
> > IMO, if a local slot exists which is a physical one, it will be a user
> > created slot and in that case worker will error out on finding
> > existing slot with same name. And the case where local slot is
> > physical one but not user-created is not possible on standby (assuming
> > we have correct check on primary disallowing setting 'failover'
> > property for physical slot). Do you have some other scenario in mind,
> > which I am missing here?
>
> I was thinking about the race condition when it has confirmed that the slot is
> not a user created one and enter "sync_state == SYNCSLOT_STATE_READY" branch,
> but at this moment, if someone uses "DROP_REPLICATION_SLOT" to drop this slot and
> recreate another one(e.g. a physical one), then the slotsync worker will
> overwrite the fields of this physical slot. Although this affects user created
> logical slots in similar cases as well.
>

User can not  drop the synced slots on standby. It should result in
ERROR. Currently we emit this error in pg_drop_replication_slot(),
same is needed in  "DROP_REPLICATION_SLOT" replication cmd. I will
change it. Thanks for raising this point.  I think, after this ERROR,
there is no need to worry about physical slots handling in
synchronize_one_slot().

> And the same is true for slotsync_drop_initiated_slots() and
> drop_obsolete_slots(), as we don't lock the slots in the list, if user tri to
> drop and re-create old slot concurrently, then we could drop user created slot
> here.
>






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 06:49               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-28 09:40                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-28 15:58                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 09:11                     ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-01 04:10                       ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-01 04:51                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-01 05:47                           ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-01 07:17                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-01 09:33                               ` shveta malik <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: shveta malik @ 2023-12-01 09:33 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; +Cc: Amit Kapila <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Drouvot, Bertrand <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Fri, Dec 1, 2023 at 12:47 PM shveta malik <[email protected]> wrote:
>
> On Fri, Dec 1, 2023 at 11:17 AM Zhijie Hou (Fujitsu)
> <[email protected]> wrote:
> >
> > On Friday, December 1, 2023 12:51 PM shveta malik <[email protected]> wrote:
> >
> > Hi,
> >
> > >
> > > On Fri, Dec 1, 2023 at 9:40 AM Zhijie Hou (Fujitsu)
> > > <[email protected]> wrote:
> > > >
> > > > On Wednesday, November 29, 2023 5:12 PM Zhijie Hou (Fujitsu)
> > > <[email protected]> wrote:
> > > >
> > > > I was reviewing slotsync worker design and here
> > > > are few comments on 0002 patch:
> > >
> > > Thanks for reviewing the patch.
> > >
> > > >
> > > >
> > > > 3. In synchronize_one_slot, do we need to skip the slot sync and drop if the
> > > > local slot is a physical one ?
> > > >
> > >
> > > IMO, if a local slot exists which is a physical one, it will be a user
> > > created slot and in that case worker will error out on finding
> > > existing slot with same name. And the case where local slot is
> > > physical one but not user-created is not possible on standby (assuming
> > > we have correct check on primary disallowing setting 'failover'
> > > property for physical slot). Do you have some other scenario in mind,
> > > which I am missing here?
> >
> > I was thinking about the race condition when it has confirmed that the slot is
> > not a user created one and enter "sync_state == SYNCSLOT_STATE_READY" branch,
> > but at this moment, if someone uses "DROP_REPLICATION_SLOT" to drop this slot and
> > recreate another one(e.g. a physical one), then the slotsync worker will
> > overwrite the fields of this physical slot. Although this affects user created
> > logical slots in similar cases as well.
> >
>
> User can not  drop the synced slots on standby. It should result in
> ERROR. Currently we emit this error in pg_drop_replication_slot(),
> same is needed in  "DROP_REPLICATION_SLOT" replication cmd. I will
> change it. Thanks for raising this point.  I think, after this ERROR,
> there is no need to worry about physical slots handling in
> synchronize_one_slot().
>
> > And the same is true for slotsync_drop_initiated_slots() and
> > drop_obsolete_slots(), as we don't lock the slots in the list, if user tri to
> > drop and re-create old slot concurrently, then we could drop user created slot
> > here.
> >


PFA v42. Changes:

v42-0001: addressed comments in [1]. Thanks Hou-San for working on this.

v42-0002: addressed comments in [2] and [3]

[1]: https://www.postgresql.org/message-id/CAHut%2BPsMTvrwUBtcHff0CG_j-ALSuEta8xC1R_k0kjR%2B9A6ehg%40mail...
[2]: https://www.postgresql.org/message-id/CAFPTHDb8LW4i9-nyvz%2BXVkJmmciZwYGivpH%3DaDOrDkBfHR_q9w%40mail...
[3]: https://www.postgresql.org/message-id/OS0PR01MB571678BABEDBE830062CAB119481A%40OS0PR01MB5716.jpnprd0...

thanks
Shveta


Attachments:

  [application/octet-stream] v42-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch (7.8K, ../../CAJpy0uCohHzphuvY-yadORvK0cjJT4vXbR=Ti+ea-Xh0DBPi4Q@mail.gmail.com/2-v42-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch)
  download | inline diff:
From ba97854e2d619c8866e82ac11849601a8a6629d8 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Wed, 29 Nov 2023 16:38:36 +0800
Subject: [PATCH v42 3/3] Allow slot-sync worker to wait for the cascading
 standbys.

The GUC standby_slot_names is needed to be set on first standby
in order to allow it to wait for confirmation for cascading
standbys before updating logical 'synced' slots in slot-sync worker.
The intent is that the logical slots (synced ones) should not go
ahead of cascading standbys.

For the user created slots on first standby, we already have this wait
logic in place in logical walsender and in pg_logical_slot_get_changes_guts(),
but for synced slots (which can not be consumed yet), we need to make
sure that they are not going ahead of cascading standbys and that is
acheived by introducing the wait in slot-sync worker before we actually
update the slots.
---
 src/backend/replication/logical/slotsync.c | 89 ++++++++++++++++++++--
 src/backend/replication/walsender.c        |  4 +-
 src/include/replication/walsender.h        |  5 ++
 3 files changed, 88 insertions(+), 10 deletions(-)

diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 506723f3b0..af982c006d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -108,7 +108,9 @@ static TimestampTz last_update_time;
  */
 #define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
 
-static void ProcessSlotSyncInterrupts(WalReceiverConn **wrconn);
+static void ProcessSlotSyncInterrupts(WalReceiverConn **wrconn,
+									  List **standby_slots);
+
 
 /*
  * Wait for remote slot to pass locally reserved position.
@@ -164,7 +166,7 @@ wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
 		CHECK_FOR_INTERRUPTS();
 
 		/* Handle any termination request if any */
-		ProcessSlotSyncInterrupts(&wrconn);
+		ProcessSlotSyncInterrupts(&wrconn, NULL /* standby_slots */ );
 
 		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
 
@@ -480,6 +482,52 @@ construct_slot_query(StringInfo s)
 					 " WHERE failover and sync_state != 'i'");
 }
 
+/*
+ * Wait for cascading physical standbys corresponding to physical slots
+ * specified in standby_slot_names GUC to confirm receiving given lsn.
+ */
+static void
+wait_for_standby_confirmation(XLogRecPtr wait_for_lsn,
+							  WalReceiverConn *wrconn)
+{
+	List	   *standby_slots;
+
+	/* Nothing to be done */
+	if (strcmp(standby_slot_names, "") == 0)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	for (;;)
+	{
+		int			rc;
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		/*
+		 * This will reload configuration and will refresh the standby_slots
+		 * as well provided standby_slot_names GUC is changed by the user.
+		 */
+		ProcessSlotSyncInterrupts(&wrconn, &standby_slots);
+
+		/*
+		 * XXX: Is waiting for 5 second before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   5000L,
+					   WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+}
+
 /*
  * Synchronize single slot to given position.
  *
@@ -760,6 +808,7 @@ synchronize_slots(WalReceiverConn *wrconn)
 	List	   *remote_slot_list = NIL;
 	MemoryContext oldctx = CurrentMemoryContext;
 	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	XLogRecPtr	max_confirmed_lsn = 0;
 	ListCell   *cell;
 	bool		slot_updated = false;
 	TimestampTz now;
@@ -817,6 +866,9 @@ synchronize_slots(WalReceiverConn *wrconn)
 		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
 		Assert(!isnull);
 
+		if (remote_slot->confirmed_lsn > max_confirmed_lsn)
+			max_confirmed_lsn = remote_slot->confirmed_lsn;
+
 		/*
 		 * It is possible to get null values for LSN and Xmin if slot is
 		 * invalidated on the primary server, so handle accordingly.
@@ -860,6 +912,17 @@ synchronize_slots(WalReceiverConn *wrconn)
 	 */
 	drop_obsolete_slots(remote_slot_list);
 
+	/*
+	 * If there are cascading standbys, wait for their confirmation before we
+	 * update synced logical slots locally.
+	 *
+	 * Instead of waiting on confirmation for lsn of each slot, let us wait
+	 * once for confirmation on max_confirmed_lsn. If that is confirmed by
+	 * each cascading standby, we are good to update all the slots.
+	 */
+	if (remote_slot_list)
+		wait_for_standby_confirmation(max_confirmed_lsn, wrconn);
+
 	/* Now sync the slots locally */
 	foreach(cell, remote_slot_list)
 	{
@@ -979,7 +1042,7 @@ validate_slotsync_parameters(char **dbname)
  * if validaity fails.
  */
 static void
-slotsync_reread_config()
+slotsync_reread_config(List **standby_slots)
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 	char	   *slotname = pstrdup(PrimarySlotName);
@@ -991,8 +1054,14 @@ slotsync_reread_config()
 	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
 	Assert(dbname);
 
-	ConfigReloadPending = false;
-	ProcessConfigFile(PGC_SIGHUP);
+	/*
+	 * Reload configs and recreate the standby_slot_names_list if GUC
+	 * standby_slot_names changed.
+	 */
+	if (standby_slots)
+		WalSndRereadConfigAndReInitSlotList(standby_slots);
+	else
+		ProcessConfigFile(PGC_SIGHUP);
 
 	if ((strcmp(conninfo, PrimaryConnInfo) != 0) ||
 		(strcmp(slotname, PrimarySlotName) != 0) ||
@@ -1029,7 +1098,8 @@ slotsync_reread_config()
  * Interrupt handler for main loop of slot sync worker.
  */
 static void
-ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
+ProcessSlotSyncInterrupts(WalReceiverConn **wrconn,
+						  List **standby_slots)
 {
 	CHECK_FOR_INTERRUPTS();
 
@@ -1045,7 +1115,10 @@ ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
 
 
 	if (ConfigReloadPending)
-		slotsync_reread_config();
+	{
+		ConfigReloadPending = false;
+		slotsync_reread_config(standby_slots);
+	}
 }
 
 /*
@@ -1113,7 +1186,7 @@ ReplSlotSyncWorkerMain(Datum main_arg)
 		int			rc;
 		long		naptime;
 
-		ProcessSlotSyncInterrupts(&wrconn);
+		ProcessSlotSyncInterrupts(&wrconn, NULL /* standby_slots */ );
 
 		naptime = synchronize_slots(wrconn);
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 7b6962f354..537b5e27dc 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1609,7 +1609,7 @@ PhysicalWakeupLogicalWalSnd(void)
  * Reload the config file and reinitialize the standby slot list if the GUC
  * standby_slot_names has changed.
  */
-static void
+void
 WalSndRereadConfigAndReInitSlotList(List **standby_slots)
 {
 	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
@@ -1634,7 +1634,7 @@ WalSndRereadConfigAndReInitSlotList(List **standby_slots)
  * it removes slots that have been invalidated, dropped, or converted to
  * logical slots.
  */
-static void
+void
 WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 {
 	ListCell   *lc;
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 1fcc22a127..c5c4714788 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -15,6 +15,7 @@
 #include <signal.h>
 
 #include "access/xlogdefs.h"
+#include "nodes/pg_list.h"
 
 /*
  * What to do with a snapshot in create replication slot command.
@@ -50,7 +51,11 @@ extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn,
+									 List **standby_slots);
 extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
+extern List *WalSndGetStandbySlots(void);
+extern void WalSndRereadConfigAndReInitSlotList(List **standby_slots);
 
 /*
  * Remember that we want to wakeup walsenders later
-- 
2.34.1



  [application/octet-stream] v42-0002-Add-logical-slot-sync-capability-to-the-physical.patch (80.2K, ../../CAJpy0uCohHzphuvY-yadORvK0cjJT4vXbR=Ti+ea-Xh0DBPi4Q@mail.gmail.com/3-v42-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From 28b59eaafdc578c04a99d4dfc112c7fa8c4b8f47 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Mon, 27 Nov 2023 16:47:02 +0800
Subject: [PATCH v42 2/3] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover. All the failover logical
replication slots on the primary (assuming configurations are
appropriate) are automatically created on the physical standbys and
are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The nap time of worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. And if
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated. If a logical slot on the primary is valid but is invalidated
on the standby due to conflict (say required rows removed on the primary),
then that slot is dropped and recreated on the standby in next sync-cycle.
It is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently).

Slots synced on the standby can be identified using 'sync_state' column of
pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but waiting for the remote slot on the
     primary server to catch up.
'r': ready for periodic syncs.
---
 doc/src/sgml/bgworker.sgml                    |   16 +-
 doc/src/sgml/config.sgml                      |   35 +-
 doc/src/sgml/logicaldecoding.sgml             |   13 +
 doc/src/sgml/system-views.sgml                |   25 +
 src/backend/access/transam/xlogrecovery.c     |   10 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    4 +
 src/backend/postmaster/postmaster.c           |    6 +
 .../libpqwalreceiver/libpqwalreceiver.c       |   44 +-
 src/backend/replication/logical/Makefile      |    1 +
 src/backend/replication/logical/logical.c     |   20 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1250 +++++++++++++++++
 src/backend/replication/logical/tablesync.c   |    1 +
 src/backend/replication/slot.c                |   22 +-
 src/backend/replication/slotfuncs.c           |   37 +-
 src/backend/replication/walsender.c           |    6 +-
 src/backend/storage/ipc/ipci.c                |    2 +
 src/backend/tcop/postgres.c                   |   12 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   10 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/commands/subscriptioncmds.h       |    4 +
 src/include/postmaster/bgworker.h             |    1 +
 src/include/replication/slot.h                |   22 +-
 src/include/replication/walreceiver.h         |   19 +
 src/include/replication/worker_internal.h     |   12 +
 src/test/recovery/t/050_verify_slot_order.pl  |  127 ++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    2 +
 32 files changed, 1696 insertions(+), 30 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2c393385a9..377f301b63 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -121,11 +121,17 @@ typedef struct BackgroundWorker
    <literal>BgWorkerStart_ConsistentState</literal> (start as soon as a consistent state
    has been reached in a hot standby, allowing processes to connect to
    databases and run read-only queries), and
-   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system has
-   entered normal read-write state).  Note the last two values are equivalent
-   in a server that's not a hot standby.  Note that this setting only indicates
-   when the processes are to be started; they do not stop when a different state
-   is reached.
+   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system
+   has entered normal read-write state. Note that the
+   <literal>BgWorkerStart_ConsistentState</literal> and
+   <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
+   in a server that's not a hot standby), and
+   <literal>BgWorkerStart_ConsistentState_HotStandby</literal> (same meaning as
+   <literal>BgWorkerStart_ConsistentState</literal> but it is more strict in
+   terms of the server i.e. start the  worker only if it is hot-standby; if it is
+   consistent state in non-standby, worker will not be started). Note that this
+   setting only indicates when the processes are to be started; they do not stop
+   when a different state is reached.
   </para>
 
   <para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 30d9b53e03..1977962caf 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4373,6 +4373,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must enable
+        <varname>enable_syncslot</varname> for the standbys to receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4566,10 +4572,15 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           A password needs to be provided too, if the sender demands password
           authentication.  It can be provided in the
           <varname>primary_conninfo</varname> string, or in a separate
-          <filename>~/.pgpass</filename> file on the standby server (use
+          <filename>~/.pgpass</filename> file on the standby server. (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          Specify <literal>dbname</literal> in
+          <varname>primary_conninfo</varname> string to allow synchronization
+          of slots from the primary server to the standby server.
+          This will only be used for slot synchronization. It is ignored
+          for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4894,6 +4905,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..90bec06f36 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,19 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and set
+     <varname>enable_syncslot</varname> on the standby. The physical replication
+     slot for the standby should be listed in
+     <varname>standby_slot_names</varname> on the primary to prevent the
+     subscriber from consuming changes faster than the hot standby.
+     Additionally, similar to creating a logical replication slot on the hot
+     standby, <varname>hot_standby_feedback</varname> should be set on the
+     standby and a physical slot between the primary and the standby should be
+     used.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..3f6e2f82c8 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,31 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has enabled slots synchronization.
+      </para>
+      <para>
+       State code:
+       <literal>n</literal> = none for user created slots,
+       <literal>i</literal> = sync initiated for the slot but slot is not ready
+        yet for periodic syncs,
+       <literal>r</literal> = ready for periodic syncs.
+      </para>
+      <para>
+      The hot standby can have any of these sync_state for the slots but on a
+      hot standby, the slots with state 'r' and 'i' can neither be used for logical
+      decoded nor dropped by the user. The primary server will have sync_state
+      as 'n' for all the slots. But if the standby is promoted to become the
+      new primary server, sync_state can be seen 'r' as well. On this new
+      primary server, slots with sync_state as 'r' and 'n' will behave the same.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c61566666a..9fb09ba0fc 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1436,15 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 911bf24a7c..92a994d424 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -22,6 +22,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "replication/worker_internal.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -130,6 +131,9 @@ static const struct
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 7a5cd06c5c..6915424b01 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -117,6 +117,7 @@
 #include "postmaster/syslogger.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -1001,6 +1002,8 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	SlotSyncWorkerRegister();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
@@ -5781,6 +5784,9 @@ bgworker_should_start_now(BgWorkerStartTime start_time)
 		case PM_HOT_STANDBY:
 			if (start_time == BgWorkerStart_ConsistentState)
 				return true;
+			else if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
+					 pmState != PM_RUN)
+				return true;
 			/* fall through */
 
 		case PM_RECOVERY:
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 7fa0bb8a3a..10c6e180c0 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -413,6 +416,45 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	PQconninfoOption *opt;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
@@ -992,7 +1034,7 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
  */
 static void
 libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
-					 bool failover)
+					bool failover)
 {
 	StringInfoData cmd;
 	PGresult   *res;
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..7de68a61e2 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,26 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	/*
+	 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+	 * promotion.
+	 */
+	if (!RecoveryInProgress() && slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+		elog(ERROR, "replication slot \"%s\" was not synced completely from the primary server",
+			 NameStr(slot->data.name));
+
+	/*
+	 * Do not allow consumption of a "synchronized" slot until the standby
+	 * gets promoted.
+	 */
+	if (RecoveryInProgress() && slot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot use replication slot \"%s\" for logical decoding",
+						NameStr(slot->data.name)),
+				 errdetail("This slot is being synced from the primary server."),
+				 errhint("Specify another replication slot.")));
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..506723f3b0
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1250 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * It also takes care of dropping the slots which were created by it and are
+ * currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/table.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_database.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/*
+ * Struct for sharing information between startup process and slot
+ * sync worker.
+ *
+ * Slot sync worker's pid is needed by startup process in order to
+ * shut it down during promotion.
+ */
+typedef struct SlotSyncWorkerCtx
+{
+	pid_t		pid;
+	slock_t		mutex;
+} SlotSyncWorkerCtx;
+
+SlotSyncWorkerCtx *SlotSyncWorker = NULL;
+
+/* GUC variable */
+bool		enable_syncslot = false;
+
+/* The last sync-cycle time when the worker updated any of the slots. */
+static TimestampTz last_update_time;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+/*
+ * Number of attempts for wait_for_primary_slot_catchup() after
+ * which it aborts the wait and the slot sync worker then moves
+ * to the next slot creation/sync.
+ */
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
+
+static void ProcessSlotSyncInterrupts(WalReceiverConn **wrconn);
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * *persist will be set to false if the slot has disappeared or was invalidated
+ * on the primary; otherwise, it will be set to true.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *persist)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+	if (persist)
+		*persist = true;
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn, confirmed_flush_lsn,"
+					 " catalog_xmin FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		XLogRecPtr	new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *slot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Handle any termination request if any */
+		ProcessSlotSyncInterrupts(&wrconn);
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot info for slot \"%s\" from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" disappeared from the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(slot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" invalidated on the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			ExecClearTuple(slot);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * Once we got valid restart_lsn, then confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(slot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\"",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up.
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			slots = lappend(slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(slots);
+}
+
+/*
+ * Get list of local logical slot names which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slot_names(void)
+{
+	List	   *localSyncedSlots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			localSyncedSlots = lappend(localSyncedSlots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return localSyncedSlots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+check_sync_slot_validity(ReplicationSlot *local_slot, List *remote_slots,
+						 bool *locally_invalidated)
+{
+	ListCell   *cell;
+
+	foreach(cell, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			SpinLockAcquire(&local_slot->mutex);
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+			SpinLockRelease(&local_slot->mutex);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Also drop the slots that are valid on the primary that got invalidated
+ * on the standby due to conflict (say required rows removed on the primary).
+ * The assumption is, that these will get recreated in next sync-cycle and
+ * it is okay to drop and recreate such slots as long as these are not
+ * consumable on the standby (which is the case currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slot_list = NIL;
+	ListCell   *lc_slot;
+
+	/*
+	 * Get the list of local 'synced' slot so that those not on remote could
+	 * be dropped.
+	 */
+	local_slot_list = get_local_synced_slot_names();
+
+	foreach(lc_slot, local_slot_list)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc_slot);
+		bool		local_exists = false;
+		bool		locally_invalidated = false;
+
+		local_exists = check_sync_slot_validity(local_slot, remote_slot_list,
+												&locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!local_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Constructs the query in order to get failover logical slots
+ * information from the primary server.
+ */
+static void
+construct_slot_query(StringInfo s)
+{
+	/*
+	 * Fetch slots with failover enabled.
+	 *
+	 * If we are on cascading standby, we should fetch only those slots from
+	 * the first standby which have sync_state as either 'n' or 'r'. Slots
+	 * with sync_state as 'i' are not sync ready yet. And when we are on the
+	 * first standby, the primary server is supposed to have slots with
+	 * sync_state as 'n' only (or it may have all 'n', 'r' and 'i' if standby
+	 * is promoted as primary). Thus in all the cases, filter sync_state !='i'
+	 * is appropriate one.
+	 */
+	appendStringInfo(s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover and sync_state != 'i'");
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ */
+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+					 bool *slot_updated)
+{
+	ReplicationSlot *s;
+	char		sync_state = 0;
+
+	/*
+	 * Make sure that concerned WAL is received before syncing slot to target
+	 * lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* Search for the named slot */
+	if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		SpinLockAcquire(&s->mutex);
+		sync_state = s->data.sync_state;
+		SpinLockRelease(&s->mutex);
+	}
+
+	StartTransactionCommand();
+
+	/*
+	 * Already existing slot (created by slot sync worker) and ready for sync,
+	 * acquire and sync it.
+	 */
+	if (sync_state == SYNCSLOT_STATE_READY)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&MyReplicationSlot->mutex);
+			MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&MyReplicationSlot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * With hot_standby_feedback enabled and invalidations handled
+		 * apropriately as above, this should never happen.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn)
+		{
+			ereport(ERROR,
+					errmsg("not synchronizing local slot \"%s\" LSN(%X/%X)"
+						   " to remote slot's LSN(%X/%X) as synchronization "
+						   " would move it backwards", remote_slot->name,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   LSN_FORMAT_ARGS(remote_slot->restart_lsn)));
+
+			goto cleanup;
+		}
+
+		if (remote_slot->confirmed_lsn != MyReplicationSlot->data.confirmed_flush ||
+			remote_slot->restart_lsn != MyReplicationSlot->data.restart_lsn ||
+			remote_slot->catalog_xmin != MyReplicationSlot->data.catalog_xmin)
+		{
+			/* Update LSN of slot to remote slot's current position */
+			local_slot_update(remote_slot);
+			ReplicationSlotSave();
+			*slot_updated = true;
+		}
+	}
+
+	/*
+	 * Already existing slot but not ready (i.e. waiting for the primary
+	 * server to catch-up), lets attempt to make it sync-ready now.
+	 */
+	else if (sync_state == SYNCSLOT_STATE_INITIATED)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&MyReplicationSlot->mutex);
+			MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&MyReplicationSlot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * Refer the slot creation part (last 'else' block) for more details
+		 * on this wait.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+			{
+				goto cleanup;
+			}
+		}
+
+		/*
+		 * Wait for primary is over, update the lsns and mark the slot as
+		 * READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Save the changes */
+		ReplicationSlotMarkDirty();
+		ReplicationSlotSave();
+
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready now",
+							remote_slot->name));
+	}
+	/* User created slot with the same name exists, raise ERROR. */
+	else if (sync_state == SYNCSLOT_STATE_NONE)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("skipping sync of slot \"%s\" as it is a user created"
+						" slot", remote_slot->name),
+				 errdetail("This slot has failover enabled on the primary and"
+						   " thus is sync candidate but user created slot with"
+						   " the same name already exists on the standby")));
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+		ReplicationSlot *slot;
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover,
+							  SYNCSLOT_STATE_INITIATED);
+
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * If the local restart_lsn and/or local catalog_xmin is ahead of
+		 * those on the remote then we cannot create the local slot in sync
+		 * with the primary server because that would mean moving the local
+		 * slot backwards and we might not have WALs retained for old LSN. In
+		 * this case we will wait for the primary server's restart_lsn and
+		 * catalog_xmin to catch up with the local one before attempting the
+		 * sync.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			bool		persist;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &persist))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid) and attempt the wait and synchronization in
+				 * the next cycle.
+				 */
+				if (persist)
+				{
+					ReplicationSlotPersist();
+					*slot_updated = true;
+				}
+
+				goto cleanup;
+			}
+		}
+
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready now",
+							remote_slot->name));
+	}
+
+cleanup:
+
+	ReplicationSlotRelease();
+	CommitTransactionCommand();
+
+	return;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and update
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns nap time for the next sync-cycle.
+ */
+static long
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *slot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	MemoryContext oldctx = CurrentMemoryContext;
+	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	ListCell   *cell;
+	bool		slot_updated = false;
+	TimestampTz now;
+
+	/* The primary_slot_name is not set yet or WALs not received yet */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		return naptime;
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* The syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	/*
+	 * Make result tuples live outside TopTransactionContext to make them
+	 * accessible even after transaction is committed.
+	 */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct query to get slots info from the primary server */
+	initStringInfo(&s);
+	construct_slot_query(&s);
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(slot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+																	   5, &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(slot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(slot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(slot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(slot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(slot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(cell, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		synchronize_one_slot(wrconn, remote_slot, &slot_updated);
+	}
+
+	now = GetCurrentTimestamp();
+
+	/*
+	 * If any of the slots get updated in this sync-cycle, retain default
+	 * naptime and update 'last_update_time' in slot sync worker. But if no
+	 * activity is observed in this sync-cycle, then increase naptime provided
+	 * inactivity time reaches threshold.
+	 */
+	if (slot_updated)
+		last_update_time = now;
+
+	else if (TimestampDifferenceExceeds(last_update_time,
+										now, WORKER_INACTIVITY_THRESHOLD_MS))
+		naptime = WORKER_INACTIVITY_NAPTIME_MS;
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	return naptime;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker", &err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Checks if GUCs are set appropriately before starting slot sync worker
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+	/*
+	 * Since 'enable_syncslot' is ON, check that other GUC settings
+	 * (primary_slot_name, hot_standby_feedback, wal_level, primary_conninfo)
+	 * are compatible with slot synchronization. If not, raise ERROR.
+	 */
+
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as primary_slot_name is not set"));
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as hot_standby_feedback is off"));
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+				errmsg("exiting slots synchronisation as it requires wal_level >= logical"));
+
+	/*
+	 * The primary_conninfo is required to make connection to primary for
+	 * getting slots information.
+	 */
+	if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as primary_conninfo is not set"));
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	*dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (*dbname == NULL)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as dbname is not specified in primary_conninfo"));
+
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs changed, validate the values again
+ * through validate_slotsync_parameters() which will exit the worker
+ * if validaity fails.
+ */
+static void
+slotsync_reread_config()
+{
+	char	   *conninfo = pstrdup(PrimaryConnInfo);
+	char	   *slotname = pstrdup(PrimarySlotName);
+	bool		syncslot = enable_syncslot;
+	bool		standbyfeedback = hot_standby_feedback;
+	bool		revalidate = false;
+	char	   *dbname;
+
+	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	Assert(dbname);
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if ((strcmp(conninfo, PrimaryConnInfo) != 0) ||
+		(strcmp(slotname, PrimarySlotName) != 0) ||
+		(syncslot != enable_syncslot) ||
+		(standbyfeedback != hot_standby_feedback))
+	{
+		revalidate = true;
+	}
+
+	pfree(conninfo);
+	pfree(slotname);
+
+	if (revalidate)
+	{
+		char	   *new_dbname;
+
+		validate_slotsync_parameters(&new_dbname);
+
+		/*
+		 * Since we have initialized this worker with old dbname, thus exit if
+		 * dbname changed. Let it get restarted and connect to new dbname
+		 * specified.
+		 */
+		if (strcmp(dbname, new_dbname) != 0)
+		{
+			ereport(ERROR,
+					errmsg("exiting slot sync woker as dbname in primary_conninfo changed"));
+		}
+	}
+}
+
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting"
+					   " down on receiving SIGINT"));
+
+		walrcv_disconnect(*wrconn);
+		proc_exit(0);
+	}
+
+
+	if (ConfigReloadPending)
+		slotsync_reread_config();
+}
+
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = 0;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	Assert(SlotSyncWorker->pid == 0);
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	validate_slotsync_parameters(&dbname);
+
+	/*
+	 * Connect to the database specified by user in PrimaryConnInfo. We need a
+	 * database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime;
+
+		ProcessSlotSyncInterrupts(&wrconn);
+
+		naptime = synchronize_slots(wrconn);
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsSlotSyncWorker(void)
+{
+	return SlotSyncWorker->pid == MyProcPid;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (!SlotSyncWorker->pid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Wait for it to die. */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long. */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (!SlotSyncWorker->pid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Allocate and initialize slow sync worker shared memory
+ */
+void
+SlotSyncWorkerShmemInit(void)
+{
+	Size		size;
+	bool		found;
+
+	size = sizeof(SlotSyncWorkerCtx);
+	size = MAXALIGN(size);
+
+	SlotSyncWorker = (SlotSyncWorkerCtx *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
+
+	if (!found)
+	{
+		memset(SlotSyncWorker, 0, size);
+		SpinLockInit(&SlotSyncWorker->mutex);
+	}
+}
+
+/*
+ * Register the background worker for slots synchronization provided
+ * enable_syncslot is ON.
+ */
+void
+SlotSyncWorkerRegister(void)
+{
+	BackgroundWorker bgw;
+
+	if (!enable_syncslot)
+	{
+		ereport(LOG,
+				errmsg("skipping slots synchronization as enable_syncslot is disabled."));
+		return;
+	}
+
+	memset(&bgw, 0, sizeof(bgw));
+
+	/* We need database connection which needs shared-memory access as well. */
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+
+	/* Start as soon as a consistent state has been reached in a hot standby */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState_HotStandby;
+
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "replication slot sync worker");
+	snprintf(bgw.bgw_type, BGW_MAXLEN,
+			 "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
+	bgw.bgw_notify_pid = 0;
+	bgw.bgw_main_arg = (Datum) 0;
+
+	RegisterBackgroundWorker(&bgw);
+}
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index d2d8bf1a7a..f1675dbd85 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "commands/subscriptioncmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index d35e6dec55..eac8fe2fe8 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -112,7 +113,7 @@ int			max_replication_slots = 10; /* the maximum number of replication
 char	   *standby_slot_names;
 
 /* This is parsed and cached list for raw standby_slot_names. */
-static List	   *standby_slot_names_list = NIL;
+static List *standby_slot_names_list = NIL;
 
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
@@ -264,7 +265,7 @@ ReplicationSlotValidateName(const char *name, int elevel)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover)
+					  bool two_phase, bool failover, char sync_state)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -325,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = sync_state;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -684,12 +686,26 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+	{
+		ReplicationSlotRelease();
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary.")));
+	}
+
 	ReplicationSlotDropAcquired();
 }
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index fb6e37d2c3..79b9ca5704 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -44,7 +44,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false);
+						  false, SYNCSLOT_STATE_NONE);
 
 	if (immediately_reserve)
 	{
@@ -137,7 +137,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover);
+						  failover, SYNCSLOT_STATE_NONE);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 58e298af89..7b6962f354 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1071,7 +1071,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false);
+							  false, false, SYNCSLOT_STATE_NONE);
 
 		if (reserve_wal)
 		{
@@ -1102,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover);
+							  two_phase, failover, SYNCSLOT_STATE_NONE);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, true);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index a3d8eacb8d..44668d8efd 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -36,6 +36,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -293,6 +294,7 @@ CreateSharedMemoryAndSemaphores(void)
 	WalRcvShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7298a187d1..92ea349488 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -60,6 +60,7 @@
 #include "replication/logicalworker.h"
 #include "replication/slot.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
@@ -3286,6 +3287,17 @@ ProcessInterrupts(void)
 			 */
 			proc_exit(1);
 		}
+		else if (IsSlotSyncWorker())
+		{
+			ereport(DEBUG1,
+					(errmsg_internal("replication slot sync worker is shutting down due to administrator command")));
+
+			/*
+			 * Slot sync worker can be stopped at any time.
+			 * Use exit status 1 so the background worker is restarted.
+			 */
+			proc_exit(1);
+		}
 		else if (IsBackgroundWorker)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 4b776266a4..2b40d5db6e 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2021,6 +2022,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_POSTMASTER, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5d940b72cd..0d400bf473 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -358,6 +358,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = on			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d906734750..6a8192ad83 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 214dc6c29e..75b4b2040d 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -17,6 +17,7 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"
 
 extern ObjectAddress CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										bool isTopLevel);
@@ -28,4 +29,7 @@ extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
 extern char defGetStreamingMode(DefElem *def);
 
+extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn,
+										char *slotname, bool missing_ok);
+
 #endif							/* SUBSCRIPTIONCMDS_H */
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index e90ff376a6..8559900b70 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -79,6 +79,7 @@ typedef enum
 	BgWorkerStart_PostmasterStart,
 	BgWorkerStart_ConsistentState,
 	BgWorkerStart_RecoveryFinished,
+	BgWorkerStart_ConsistentState_HotStandby,
 } BgWorkerStartTime;
 
 #define BGW_DEFAULT_RESTART_INTERVAL	60
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index ca06e5b1ad..3cfcfef638 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -15,7 +15,6 @@
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "storage/spin.h"
-#include "replication/walreceiver.h"
 
 /*
  * Behaviour of replication slots, upon release or crash.
@@ -52,6 +51,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +119,13 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Is this a slot created by a sync-slot worker?
+	 *
+	 * Relevant for logical slots on the physical standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)?
 	 * Only relevant for logical slots on the primary server.
@@ -225,9 +239,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover);
+								  bool two_phase, bool failover,
+								  char sync_state);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
@@ -253,7 +268,6 @@ extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_l
 extern int	ReplicationSlotIndex(ReplicationSlot *slot);
 extern bool ReplicationSlotName(int index, Name name);
 extern void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot);
-extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok);
 
 extern void StartupReplicationSlots(void);
 extern void CheckPointReplicationSlots(bool is_shutdown);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 61bc8de72c..2f7ae9dc69 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -20,6 +20,7 @@
 #include "pgtime.h"
 #include "port/atomics.h"
 #include "replication/logicalproto.h"
+#include "replication/slot.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/latch.h"
@@ -280,6 +281,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -404,6 +420,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -429,6 +446,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 4378690ab0..3379b1ffbf 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -239,6 +239,11 @@ extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
+/* Slot sync worker objects */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+extern PGDLLIMPORT bool enable_syncslot;
+
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
@@ -328,6 +333,13 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
+extern void SlotSyncWorkerRegister(void);
+extern void ShutDownSlotSync(void);
+extern void slotsync_drop_initiated_slots(void);
+extern bool IsSlotSyncWorker(void);
+extern void SlotSyncWorkerShmemInit(void);
+
 #define isParallelApplyWorker(worker) ((worker)->in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
 #define isTablesyncWorker(worker) ((worker)->in_use && \
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
index bff0f52a46..e7a00bde12 100644
--- a/src/test/recovery/t/050_verify_slot_order.pl
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -146,4 +146,131 @@ $result = $subscriber1->safe_psql('postgres',
 	"SELECT count(*) = $primary_row_count FROM tab_int;");
 is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
 
+# Test logical failover slots on the standby
+# Configure standby3 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub1_slot->| ----> subscriber1 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb3_slot--->| ----> standby3 (connected via streaming replication)
+#                                        |                 lsub1_slot(synced_slot)
+
+# Cleanup old standby_slot_names
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = ''
+));
+$primary->start;
+
+$primary->psql('postgres',
+		q{SELECT pg_create_physical_replication_slot('sb3_slot');});
+
+$backup_name = 'backup2';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+my $connstr_1 = $primary->connstr;
+$standby3->stop;
+$standby3->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb3_slot'
+});
+$standby3->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+$standby3->start;
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb3_slot'
+));
+$primary->start;
+
+# Restart the standby
+$standby3->restart;
+
+# Wait for the standby to start sync
+my $offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots;}
+  ),
+  'lsub1_slot',
+  'failover slot was created');
+
+# Verify slot properties on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|r",
+  'logical slot has sync_state as ready and failover as true on standby');
+
+# Verify slot properties on the primary
+is( $primary->safe_psql('postgres',
+    q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|n",
+  'logical slot has sync_state as none and failover as true on primary');
+
+# Test to confirm that restart_lsn of the logical slot on the primary is synced to the standby
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_int;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# let the slots get synced on the standby
+sleep 2;
+
+# Get the restart_lsn for the logical slot lsub1_slot on the primary
+my $primary_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that restart_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'restart_lsn of slot lsub1_slot synced to standby');
+
+# Get the confirmed_flush_lsn for the logical slot lsub1_slot on the primary
+$primary_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that confirmed_flush_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub1_slot synced to the standby');
+
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5f50368e35..79c5f571ed 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2317,6 +2317,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2575,6 +2576,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorkerCtx
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.34.1



  [application/octet-stream] v42-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (136.6K, ../../CAJpy0uCohHzphuvY-yadORvK0cjJT4vXbR=Ti+ea-Xh0DBPi4Q@mail.gmail.com/4-v42-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From c2c65997267b99e85de184fa679b083917ab7fe3 Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Tue, 21 Nov 2023 14:50:21 +0530
Subject: [PATCH v42 1/3] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to
the physical standbys so that logical replication can be resumed after
failover. It is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API.

Ex1:
CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

Ex2: (failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

A new replication command called ALTER_REPLICATION_SLOT and a
corresponding walreceiver API function named walrcv_alter_slot have been
implemented. They allow subscribers or users to modify the failover
property of a replication slot on the publisher.

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).
---
 contrib/test_decoding/expected/slot.out       |  58 +++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |  20 +-
 doc/src/sgml/ref/create_subscription.sgml     |  23 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       | 108 +++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  38 +-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  53 ++-
 src/backend/replication/logical/worker.c      |  71 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 182 +++++++++-
 src/backend/replication/slotfuncs.c           |  19 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 336 ++++++++++++++++--
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_upgrade/info.c                     |   5 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 src/test/recovery/t/050_verify_slot_order.pl  | 149 ++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 +++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 48 files changed, 1360 insertions(+), 168 deletions(-)
 create mode 100644 src/test/recovery/t/050_verify_slot_order.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 94d1eb2b81..30d9b53e03 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4360,6 +4360,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..481e397bad 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
@@ -230,6 +233,17 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
+
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property of the new slot may differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription. When creating the slot,
+      ensure the slot failover property matches the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter value of the subscription.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..fa6cd1c43f 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,29 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..e74fff2305 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,34 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If the slot_name is specified without the create_slot option, it
+			 * is possible that the user intends to use an existing slot on the
+			 * publisher, so here we alter the failover property of the slot to
+			 * match the failover value in subscription.
+			 *
+			 * We do not need to change the failover to false if the server
+			 * does not support failover (e.g. pre-PG17)
+			 */
+			else if (opts.slot_name &&
+					(failover_enabled || walrcv_server_version(wrconn) >= 170000))
+			{
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+								opts.slot_name,
+								failover_enabled ? "true" : "false")));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,13 +1331,21 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
@@ -1334,13 +1394,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 						/* translator: %s is an SQL ALTER command */
 								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
 										 isadd ?
@@ -1389,7 +1461,19 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 					ereport(ERROR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "two_phase"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "failover"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 60d5c1fc40..7fa0bb8a3a 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -883,8 +887,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -913,7 +917,8 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+			appendStringInfoString(&cmd, "FAILOVER, ");
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -982,6 +987,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					 bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index df3c42eb5d..d2d8bf1a7a 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -614,15 +614,28 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "two_phase")));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "failover")));
+
 				should_exit = true;
 			}
 		}
@@ -1420,7 +1433,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1722,10 +1736,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1733,9 +1749,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1749,9 +1764,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..cf5d9caa8b 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,6 +132,37 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * failover = true when creating the subscription. Enabling failover allows us
+ * to smoothly transition to the promoted standby, ensuring that we can
+ * subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby. During syncing, the local restart_lsn and/or local catalog_xmin of
+ * the newly created slot on the standby are typically ahead of those on the
+ * primary. Therefore, the standby needs to wait for the primary server's
+ * restart_lsn and catalog_xmin to catch up, which takes time.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
@@ -3947,6 +3978,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4514,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4572,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4612,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..d35e6dec55 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List	   *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -251,7 +263,8 @@ ReplicationSlotValidateName(const char *name, int elevel)
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +324,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +693,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2198,140 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+
+	/* Need a modifiable copy of string */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into list of identifiers */
+	if (!SplitIdentifierString(rawname, ',', &elemlist))
+	{
+		/* syntax error in name list */
+		GUC_check_errdetail("List syntax is invalid.");
+		goto ret_standby_slot_names_ng;
+	}
+
+	/*
+	 * Verify 'type' of slot now.
+	 *
+	 * Skip check if replication slots' data is not initialized yet i.e. we
+	 * are in startup process.
+	 */
+	if (!ReplicationSlotCtl)
+		goto ret_standby_slot_names_ok;
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+			goto ret_standby_slot_names_ng;
+
+		if (!SlotIsPhysical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			goto ret_standby_slot_names_ng;
+		}
+	}
+
+ret_standby_slot_names_ok:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return true;
+
+ret_standby_slot_names_ng:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return false;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..fb6e37d2c3 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -679,6 +689,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +745,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +785,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 2398167f49..e27d231174 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -386,7 +386,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..58e298af89 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool	failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,238 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up the logical walsender processes with failover-enabled slots if the
+ * physical slot of the current walsender is specified in standby_slot_names
+ * GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn. Additionally,
+ * it removes slots that have been invalidated, dropped, or converted to
+ * logical slots.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
+		bool		invalidated = false;
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (slot && SlotIsPhysical(slot))
+		{
+			SpinLockAcquire(&slot->mutex);
+			restart_lsn = slot->data.restart_lsn;
+			invalidated = slot->data.invalidated != RS_INVAL_NONE;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Continue if the current slot hasn't caught up. */
+		if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
+			restart_lsn < wait_for_lsn)
+		{
+			/* Log warning if no active_pid for this physical slot */
+			if (slot->active_pid == 0)
+				ereport(WARNING,
+						errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+							   name, "standby_slot_names"),
+						errdetail("Logical replication is waiting on the "
+								  "standby associated with \"%s\"", name),
+						errhint("Consider starting standby associated with "
+								"\"%s\" or amend standby_slot_names", name));
+
+			continue;
+		}
+
+		/*
+		 * It may happen that the slot specified in standby_slot_names GUC
+		 * value is dropped, so let's skip over it.
+		 */
+		else if (!slot)
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+
+		/*
+		 * If a logical slot name is provided in standby_slot_names, issue a
+		 * WARNING and skip it. Although logical slots are disallowed in the
+		 * GUC check_hook(validate_standby_slots), it is still possible for a
+		 * user to drop an existing physical slot and recreate a logical slot
+		 * with the same name. Since it is harmless, a WARNING should be
+		 * enough, no need to error-out.
+		 */
+		else if (SlotIsLogical(slot))
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+
+		/*
+		 * Specified physical slot may have been invalidated, so no point in
+		 * waiting for it.
+		 */
+		else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
+			warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+		else
+			Assert(restart_lsn >= wait_for_lsn);
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving the given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, the
+ * function also waits for all the specified streaming replication standby
+ * servers to confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr if we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
-	if (RecentFlushPtr != InvalidXLogRecPtr &&
-		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	if (!XLogRecPtrIsInvalid(RecentFlushPtr) && loc <= RecentFlushPtr)
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know we
+		 * have enough WAL available and all the standby servers have confirmed
+		 * receipt of WAL up to RecentFlushPtr. This is particularly interesting
+		 * if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1830,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1845,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,8 +1884,14 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
 			break;
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
@@ -1654,9 +1932,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2099,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2336,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3599,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3670,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another CV
+	 * that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 6474e35ec0..4b776266a4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4572,6 +4572,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..5d940b72cd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -329,6 +329,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..e16286f18c 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,6 +679,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
@@ -687,6 +688,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +699,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..f6ac78418e 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced
+								 * to the physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 087a4cd6e8..bdee3b8206 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -159,7 +159,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -173,8 +173,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 049801186c..905964a2e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3327,7 +3327,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb58dee3bc..d906734750 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..ca06e5b1ad 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)?
+	 * Only relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 04b439dc50..61bc8de72c 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -356,9 +356,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									   const char *slotname,
+									   bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -400,6 +411,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -429,8 +441,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 268f8e8d0f..1fcc22a127 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -14,6 +14,8 @@
 
 #include <signal.h>
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -47,6 +49,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 47854b5cd4..4378690ab0 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -258,7 +258,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..3be3ee52fc 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_verify_slot_order.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
new file mode 100644
index 0000000000..bff0f52a46
--- /dev/null
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -0,0 +1,149 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres', "CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+$subscriber1->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true
+$subscriber1->safe_psql('postgres',
+		"CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);");
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+$subscriber2->safe_psql('postgres',
+		"CREATE SUBSCRIPTION mysub2 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);");
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby comes
+# up.
+$standby1->stop;
+
+# Create some data on primary
+my $primary_row_count = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = 0 FROM tab_int;");
+is($result, 't', "subscriber1 doesn't get data from primary until standby1 acknowledges changes");
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d659adbfd6..5f50368e35 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3865,6 +3866,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-11-28 12:06           ` Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2 siblings, 1 reply; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-11-28 12:06 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; shveta malik <[email protected]>; +Cc: Peter Smith <[email protected]>; Amit Kapila <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 11/27/23 9:57 AM, Zhijie Hou (Fujitsu) wrote:
> On Monday, November 27, 2023 4:51 PM shveta malik <[email protected]> wrote:
> 
> Here is the updated version(v39_2) which include all the changes made in 0002.
> Please use for review, and sorry for the confusion.
> 

Thanks!

As far v39_2-0001:

"
     Altering the failover option of the subscription is currently not
     permitted. However, this restriction may be lifted in future versions.
"

Should we mention that we can alter the related replication slot?

+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>

I think we have a corner case here. If one alter the replication slot on the primary
then "subfailoverstate" is not updated accordingly on the subscriber. Given the 2 remarks above
would that make sense to prevent altering a replication slot associated to a subscription?

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-11-29 05:58             ` Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-11-29 05:58 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; shveta malik <[email protected]>; +Cc: Peter Smith <[email protected]>; Amit Kapila <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Tuesday, November 28, 2023 8:07 PM Drouvot, Bertrand <[email protected]> wrote:

Hi,

> On 11/27/23 9:57 AM, Zhijie Hou (Fujitsu) wrote:
> > On Monday, November 27, 2023 4:51 PM shveta malik
> <[email protected]> wrote:
> >
> > Here is the updated version(v39_2) which include all the changes made in
> 0002.
> > Please use for review, and sorry for the confusion.
> >
> 
> Thanks!
> 
> As far v39_2-0001:
> 
> "
>      Altering the failover option of the subscription is currently not
>      permitted. However, this restriction may be lifted in future versions.
> "
> 
> Should we mention that we can alter the related replication slot?

Will add.

> 
> +         <para>
> +          The implementation of failover requires that replication
> +          has successfully finished the initial table synchronization
> +          phase. So even when <literal>failover</literal> is enabled for a
> +          subscription, the internal failover state remains
> +          temporarily <quote>pending</quote> until the initialization
> phase
> +          completes. See column
> <structfield>subfailoverstate</structfield>
> +          of <link
> linkend="catalog-pg-subscription"><structname>pg_subscription</structna
> me></link>
> +          to know the actual failover state.
> +         </para>
> 
> I think we have a corner case here. If one alter the replication slot on the
> primary then "subfailoverstate" is not updated accordingly on the subscriber.
> Given the 2 remarks above would that make sense to prevent altering a
> replication slot associated to a subscription?

Thanks for the review!

I think we could not distinguish the user created logical slot or subscriber
created slot as there is no related info in slot's data. And user could change
the slot on subscription by "alter sub set (slot_name)", so maintaining this info
would need some efforts.

Besides, I think this case overlaps the previous discussed "alter sub set
(slot_name)" issue[1]. Both the cases are because the slot's failover is
different from the subscription's failover setting. I think we could handle
them similarly that user need to take care of not changing the failover to
wrong value. Or do you prefer another approach that mentioned in that thread[1]
? (always alter the slot at the startup of apply worker).

[1] https://www.postgresql.org/message-id/564b195a-180c-42e9-902b-b1a8b50218ee%40gmail.com

Best Regards,
Hou zj


^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-11-29 09:54               ` Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-04 03:33                 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  0 siblings, 2 replies; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-11-29 09:54 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; shveta malik <[email protected]>; +Cc: Peter Smith <[email protected]>; Amit Kapila <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 11/29/23 6:58 AM, Zhijie Hou (Fujitsu) wrote:
> On Tuesday, November 28, 2023 8:07 PM Drouvot, Bertrand <[email protected]> wrote:
> 
> Hi,
> 
>> On 11/27/23 9:57 AM, Zhijie Hou (Fujitsu) wrote:
>>> On Monday, November 27, 2023 4:51 PM shveta malik
>> <[email protected]> wrote:
>>>
>>> Here is the updated version(v39_2) which include all the changes made in
>> 0002.
>>> Please use for review, and sorry for the confusion.
>>>
>>
>> Thanks!
>>
>> As far v39_2-0001:
>>
>> "
>>       Altering the failover option of the subscription is currently not
>>       permitted. However, this restriction may be lifted in future versions.
>> "
>>
>> Should we mention that we can alter the related replication slot?
> 
> Will add.
> 
>>
>> +         <para>
>> +          The implementation of failover requires that replication
>> +          has successfully finished the initial table synchronization
>> +          phase. So even when <literal>failover</literal> is enabled for a
>> +          subscription, the internal failover state remains
>> +          temporarily <quote>pending</quote> until the initialization
>> phase
>> +          completes. See column
>> <structfield>subfailoverstate</structfield>
>> +          of <link
>> linkend="catalog-pg-subscription"><structname>pg_subscription</structna
>> me></link>
>> +          to know the actual failover state.
>> +         </para>
>>
>> I think we have a corner case here. If one alter the replication slot on the
>> primary then "subfailoverstate" is not updated accordingly on the subscriber.
>> Given the 2 remarks above would that make sense to prevent altering a
>> replication slot associated to a subscription?
> 
> Thanks for the review!
> 
> I think we could not distinguish the user created logical slot or subscriber
> created slot as there is no related info in slot's data.

Yeah that would need extra work.

> And user could change
> the slot on subscription by "alter sub set (slot_name)", so maintaining this info
> would need some efforts.
>

Yes.
  
> Besides, I think this case overlaps the previous discussed "alter sub set
> (slot_name)" issue[1]. Both the cases are because the slot's failover is
> different from the subscription's failover setting.

Yeah agree.

> I think we could handle
> them similarly that user need to take care of not changing the failover to
> wrong value. Or do you prefer another approach that mentioned in that thread[1]
> ? (always alter the slot at the startup of apply worker).
> 

I think I'm fine with documenting the fact that the user should not change the failover
value. But if he does change it (because at the end nothing prevents it to do so) then
I think the meaning of subfailoverstate should still make sense.

One way to achieve this could be to change its meaning? Say rename it to
say subfailovercreationstate (to reflect the fact that it was the state at the creation time)
and change messages like:

"
ALTER SUBSCRIPTION with refresh and copy_data is not allowed when failover is enabled
"

to something like

"
ALTER SUBSCRIPTION with refresh and copy_data is not allowed for subscription created with failover enabled"
"

and change the doc accordingly.

What do you think?

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-12-01 11:06                 ` Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Amit Kapila @ 2023-12-01 11:06 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Zhijie Hou (Fujitsu) <[email protected]>; shveta malik <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Wed, Nov 29, 2023 at 3:24 PM Drouvot, Bertrand
<[email protected]> wrote:
>
> On 11/29/23 6:58 AM, Zhijie Hou (Fujitsu) wrote:
> > On Tuesday, November 28, 2023 8:07 PM Drouvot, Bertrand <[email protected]> wrote:
> >
> > Hi,
> >
> >> On 11/27/23 9:57 AM, Zhijie Hou (Fujitsu) wrote:
> >>> On Monday, November 27, 2023 4:51 PM shveta malik
> >> <[email protected]> wrote:
> >>>
> >>> Here is the updated version(v39_2) which include all the changes made in
> >> 0002.
> >>> Please use for review, and sorry for the confusion.
> >>>
> >>
> >> Thanks!
> >>
> >> As far v39_2-0001:
> >>
> >> "
> >>       Altering the failover option of the subscription is currently not
> >>       permitted. However, this restriction may be lifted in future versions.
> >> "
> >>
> >> Should we mention that we can alter the related replication slot?
> >
> > Will add.
> >
> >>
> >> +         <para>
> >> +          The implementation of failover requires that replication
> >> +          has successfully finished the initial table synchronization
> >> +          phase. So even when <literal>failover</literal> is enabled for a
> >> +          subscription, the internal failover state remains
> >> +          temporarily <quote>pending</quote> until the initialization
> >> phase
> >> +          completes. See column
> >> <structfield>subfailoverstate</structfield>
> >> +          of <link
> >> linkend="catalog-pg-subscription"><structname>pg_subscription</structna
> >> me></link>
> >> +          to know the actual failover state.
> >> +         </para>
> >>
> >> I think we have a corner case here. If one alter the replication slot on the
> >> primary then "subfailoverstate" is not updated accordingly on the subscriber.
> >> Given the 2 remarks above would that make sense to prevent altering a
> >> replication slot associated to a subscription?
> >
> > Thanks for the review!
> >
> > I think we could not distinguish the user created logical slot or subscriber
> > created slot as there is no related info in slot's data.
>
> Yeah that would need extra work.
>
> > And user could change
> > the slot on subscription by "alter sub set (slot_name)", so maintaining this info
> > would need some efforts.
> >
>
> Yes.
>
> > Besides, I think this case overlaps the previous discussed "alter sub set
> > (slot_name)" issue[1]. Both the cases are because the slot's failover is
> > different from the subscription's failover setting.
>
> Yeah agree.
>
> > I think we could handle
> > them similarly that user need to take care of not changing the failover to
> > wrong value. Or do you prefer another approach that mentioned in that thread[1]
> > ? (always alter the slot at the startup of apply worker).
> >
>
> I think I'm fine with documenting the fact that the user should not change the failover
> value. But if he does change it (because at the end nothing prevents it to do so) then
> I think the meaning of subfailoverstate should still make sense.
>

How user can change the slot's failover property? Do we provide any
command for it?

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-12-01 11:15                   ` Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-12-01 11:15 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; +Cc: Zhijie Hou (Fujitsu) <[email protected]>; shveta malik <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 12/1/23 12:06 PM, Amit Kapila wrote:
> On Wed, Nov 29, 2023 at 3:24 PM Drouvot, Bertrand
> <[email protected]> wrote:
>> I think I'm fine with documenting the fact that the user should not change the failover
>> value. But if he does change it (because at the end nothing prevents it to do so) then
>> I think the meaning of subfailoverstate should still make sense.
>>
> 
> How user can change the slot's failover property? Do we provide any
> command for it?

It's doable, using a replication connection:

"
$ psql replication=database
psql (17devel)
Type "help" for help.

postgres=# ALTER_REPLICATION_SLOT logical_slot6 (FAILOVER false);
ALTER_REPLICATION_SLOT
"

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-12-01 12:10                     ` Nisha Moond <[email protected]>
  2023-12-01 16:00                       ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 2 replies; 113+ messages in thread

From: Nisha Moond @ 2023-12-01 12:10 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; shveta malik <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Review for v41 patch.

1.
======
src/backend/utils/misc/postgresql.conf.sample

+#enable_syncslot = on # enables slot synchronization on the physical
standby from the primary

enable_syncslot is disabled by default, so, it should be 'off' here.

~~~
2.
IIUC, the slotsyncworker's connection to the primary is to execute a
query. Its aim is not walsender type connection, but at primary when
queried, the 'backend_type' is set to 'walsender'.
Snippet from primary db-

datname  |   usename   | application_name | wait_event_type | backend_type
---------+-------------+------------------+-----------------+--------------
postgres | replication | slotsyncworker   | Client          | walsender

Is it okay?

~~~
3.
As per current logic, If there are slots on primary with disabled
subscriptions, then, when standby is created it replicates these slots
but can't make them sync-ready until any activity happens on the
slots.
So, such slots stay in 'i' sync-state and get dropped when failover
happens. Now, if the subscriber tries to enable their existing
subscription after failover, it gives an error that the slot does not
exist.

~~~
4. primary_slot_name GUC value test:

When standby is started with a non-existing primary_slot_name, the
wal-receiver gives an error but the slot-sync worker does not raise
any error/warning. It is no-op though as it has a check 'if
(XLogRecPtrIsInvalid(WalRcv->latestWalEnd)) do nothing'.   Is this
okay or shall the slot-sync worker too raise an error and exit?

In another case, when standby is started with valid primary_slot_name,
but it is changed to some invalid value in runtime, then walreceiver
starts giving error but the slot-sync worker keeps on running. In this
case, unlike the previous case, it even did not go to no-op mode (as
it sees valid WalRcv->latestWalEnd from the earlier run) and keep
pinging primary repeatedly for slots.  Shall here it should error out
or at least be no-op until we give a valid primary_slot_name?

--
Thanks,
Nisha






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
@ 2023-12-01 16:00                       ` Nisha Moond <[email protected]>
  2023-12-02 04:19                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Nisha Moond @ 2023-12-01 16:00 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; shveta malik <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Fri, Dec 1, 2023 at 5:40 PM Nisha Moond <[email protected]> wrote:
>
> Review for v41 patch.
>
> 1.
> ======
> src/backend/utils/misc/postgresql.conf.sample
>
> +#enable_syncslot = on # enables slot synchronization on the physical
> standby from the primary
>
> enable_syncslot is disabled by default, so, it should be 'off' here.
>
> ~~~
> 2.
> IIUC, the slotsyncworker's connection to the primary is to execute a
> query. Its aim is not walsender type connection, but at primary when
> queried, the 'backend_type' is set to 'walsender'.
> Snippet from primary db-
>
> datname  |   usename   | application_name | wait_event_type | backend_type
> ---------+-------------+------------------+-----------------+--------------
> postgres | replication | slotsyncworker   | Client          | walsender
>
> Is it okay?
>
> ~~~
> 3.
> As per current logic, If there are slots on primary with disabled
> subscriptions, then, when standby is created it replicates these slots
> but can't make them sync-ready until any activity happens on the
> slots.
> So, such slots stay in 'i' sync-state and get dropped when failover
> happens. Now, if the subscriber tries to enable their existing
> subscription after failover, it gives an error that the slot does not
> exist.

Is this behavior expected? If yes, then is it worth documenting about
disabled subscription slots not being synced?

--
Thanks,
Nisha






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-01 16:00                       ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
@ 2023-12-02 04:19                         ` Amit Kapila <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Amit Kapila @ 2023-12-02 04:19 UTC (permalink / raw)
  To: Nisha Moond <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; shveta malik <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Fri, Dec 1, 2023 at 9:31 PM Nisha Moond <[email protected]> wrote:
>
> On Fri, Dec 1, 2023 at 5:40 PM Nisha Moond <[email protected]> wrote:
> >
> > Review for v41 patch.
> >
> > 1.
> > ======
> > src/backend/utils/misc/postgresql.conf.sample
> >
> > +#enable_syncslot = on # enables slot synchronization on the physical
> > standby from the primary
> >
> > enable_syncslot is disabled by default, so, it should be 'off' here.
> >
> > ~~~
> > 2.
> > IIUC, the slotsyncworker's connection to the primary is to execute a
> > query. Its aim is not walsender type connection, but at primary when
> > queried, the 'backend_type' is set to 'walsender'.
> > Snippet from primary db-
> >
> > datname  |   usename   | application_name | wait_event_type | backend_type
> > ---------+-------------+------------------+-----------------+--------------
> > postgres | replication | slotsyncworker   | Client          | walsender
> >
> > Is it okay?
> >
> > ~~~
> > 3.
> > As per current logic, If there are slots on primary with disabled
> > subscriptions, then, when standby is created it replicates these slots
> > but can't make them sync-ready until any activity happens on the
> > slots.
> > So, such slots stay in 'i' sync-state and get dropped when failover
> > happens. Now, if the subscriber tries to enable their existing
> > subscription after failover, it gives an error that the slot does not
> > exist.
>
> Is this behavior expected? If yes, then is it worth documenting about
> disabled subscription slots not being synced?
>

This is expected behavior because even if we would retain such slots
(state 'i'), we won't be able to make them in the 'ready' state after
failover because we can't get the required WAL from the primary.

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
@ 2023-12-04 05:10                       ` shveta malik <[email protected]>
  2023-12-04 05:15                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  1 sibling, 2 replies; 113+ messages in thread

From: shveta malik @ 2023-12-04 05:10 UTC (permalink / raw)
  To: Nisha Moond <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Fri, Dec 1, 2023 at 5:40 PM Nisha Moond <[email protected]> wrote:
>
> Review for v41 patch.

Thanks for the feedback.

>
> 1.
> ======
> src/backend/utils/misc/postgresql.conf.sample
>
> +#enable_syncslot = on # enables slot synchronization on the physical
> standby from the primary
>
> enable_syncslot is disabled by default, so, it should be 'off' here.
>

Sure, I will change it.

> ~~~
> 2.
> IIUC, the slotsyncworker's connection to the primary is to execute a
> query. Its aim is not walsender type connection, but at primary when
> queried, the 'backend_type' is set to 'walsender'.
> Snippet from primary db-
>
> datname  |   usename   | application_name | wait_event_type | backend_type
> ---------+-------------+------------------+-----------------+--------------
> postgres | replication | slotsyncworker   | Client          | walsender
>
> Is it okay?
>

Slot sync worker uses 'libpqrcv_connect' for connection which sends
'replication'-'database' key-value pair as one of the connection
options. And on the primary side, 'ProcessStartupPacket' on the basis
of this key-value pair sets the process as walsender one (am_walsender
= true).
And thus this reflects as backend_type='walsender' in
pg_stat_activity. I do not see any harm in this backend_type for
slot-sync worker currently. This is on a similar line of connections
used for logical-replications. And since a slot-sync worker also deals
with wals-positions (lsns), it is okay to maintain backend_type as
walsender unless you (or others) see any potential issue in doing
that. So let me know.

> ~~~
> 3.
> As per current logic, If there are slots on primary with disabled
> subscriptions, then, when standby is created it replicates these slots
> but can't make them sync-ready until any activity happens on the
> slots.
> So, such slots stay in 'i' sync-state and get dropped when failover
> happens. Now, if the subscriber tries to enable their existing
> subscription after failover, it gives an error that the slot does not
> exist.
>

yes, this is expected as Amit explained in [1]. But let me review if
we need to document this case for disabled subscriptions. i.e.
disabled subscription if enabled after promotion might not work.

> ~~~
> 4. primary_slot_name GUC value test:
>
> When standby is started with a non-existing primary_slot_name, the
> wal-receiver gives an error but the slot-sync worker does not raise
> any error/warning. It is no-op though as it has a check 'if
> (XLogRecPtrIsInvalid(WalRcv->latestWalEnd)) do nothing'.   Is this
> okay or shall the slot-sync worker too raise an error and exit?
>
> In another case, when standby is started with valid primary_slot_name,
> but it is changed to some invalid value in runtime, then walreceiver
> starts giving error but the slot-sync worker keeps on running. In this
> case, unlike the previous case, it even did not go to no-op mode (as
> it sees valid WalRcv->latestWalEnd from the earlier run) and keep
> pinging primary repeatedly for slots.  Shall here it should error out
> or at least be no-op until we give a valid primary_slot_name?
>

 I reviewed it. There is no way to test the existence/validity of
'primary_slot_name' on standby without making a connection to primary.
If primary_slot_name is invalid from the start, slot-sync worker will
be no-op (as you tested) as WalRecv->latestWalENd will be invalid, and
if 'primary_slot_name' is changed to invalid on runtime, slot-sync
worker will still keep on pinging primary. But that should be okay (in
fact needed) as it needs to sync at-least the previous slot's
positions (in case it is delayed in doing so for some reason earlier).
And once the slots are up-to-date on standby, even if worker pings
primary, it will not see any change in slots lsns and thus go for
longer nap. I think, it is not worth the effort to introduce the
complexity of checking validity of 'primary_slot_name' on primary from
standby for this rare scenario.

It will be good to know thoughts of others on above 3 points.

thanks
Shveta






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-04 05:15                         ` shveta malik <[email protected]>
  1 sibling, 0 replies; 113+ messages in thread

From: shveta malik @ 2023-12-04 05:15 UTC (permalink / raw)
  To: Nisha Moond <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Mon, Dec 4, 2023 at 10:40 AM shveta malik <[email protected]> wrote:
>
> On Fri, Dec 1, 2023 at 5:40 PM Nisha Moond <[email protected]> wrote:
> >
> > Review for v41 patch.
>
> Thanks for the feedback.
>
> >
> > 1.
> > ======
> > src/backend/utils/misc/postgresql.conf.sample
> >
> > +#enable_syncslot = on # enables slot synchronization on the physical
> > standby from the primary
> >
> > enable_syncslot is disabled by default, so, it should be 'off' here.
> >
>
> Sure, I will change it.
>
> > ~~~
> > 2.
> > IIUC, the slotsyncworker's connection to the primary is to execute a
> > query. Its aim is not walsender type connection, but at primary when
> > queried, the 'backend_type' is set to 'walsender'.
> > Snippet from primary db-
> >
> > datname  |   usename   | application_name | wait_event_type | backend_type
> > ---------+-------------+------------------+-----------------+--------------
> > postgres | replication | slotsyncworker   | Client          | walsender
> >
> > Is it okay?
> >
>
> Slot sync worker uses 'libpqrcv_connect' for connection which sends
> 'replication'-'database' key-value pair as one of the connection
> options. And on the primary side, 'ProcessStartupPacket' on the basis
> of this key-value pair sets the process as walsender one (am_walsender
> = true).
> And thus this reflects as backend_type='walsender' in
> pg_stat_activity. I do not see any harm in this backend_type for
> slot-sync worker currently. This is on a similar line of connections
> used for logical-replications. And since a slot-sync worker also deals
> with wals-positions (lsns), it is okay to maintain backend_type as
> walsender unless you (or others) see any potential issue in doing
> that. So let me know.
>
> > ~~~
> > 3.
> > As per current logic, If there are slots on primary with disabled
> > subscriptions, then, when standby is created it replicates these slots
> > but can't make them sync-ready until any activity happens on the
> > slots.
> > So, such slots stay in 'i' sync-state and get dropped when failover
> > happens. Now, if the subscriber tries to enable their existing
> > subscription after failover, it gives an error that the slot does not
> > exist.
> >
>
> yes, this is expected as Amit explained in [1]. But let me review if
> we need to document this case for disabled subscriptions. i.e.
> disabled subscription if enabled after promotion might not work.

Sorry, missed to mention the link earlier:
[1]: https://www.postgresql.org/message-id/CAA4eK1J5Hxp%2BzhvptyyjqQ4JSQzwnkFRXtQn8v9opxtZmmY_Ug%40mail.g...






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-04 16:36                         ` Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-12-04 16:36 UTC (permalink / raw)
  To: shveta malik <[email protected]>; Nisha Moond <[email protected]>; +Cc: Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 12/4/23 6:10 AM, shveta malik wrote:
> On Fri, Dec 1, 2023 at 5:40 PM Nisha Moond <[email protected]> wrote:
>>
>> Review for v41 patch.
> 
> Thanks for the feedback.
> 
>> ~~~
>> 2.
>> IIUC, the slotsyncworker's connection to the primary is to execute a
>> query. Its aim is not walsender type connection, but at primary when
>> queried, the 'backend_type' is set to 'walsender'.
>> Snippet from primary db-
>>
>> datname  |   usename   | application_name | wait_event_type | backend_type
>> ---------+-------------+------------------+-----------------+--------------
>> postgres | replication | slotsyncworker   | Client          | walsender
>>
>> Is it okay?
>>
> 
> Slot sync worker uses 'libpqrcv_connect' for connection which sends
> 'replication'-'database' key-value pair as one of the connection
> options. And on the primary side, 'ProcessStartupPacket' on the basis
> of this key-value pair sets the process as walsender one (am_walsender
> = true).
> And thus this reflects as backend_type='walsender' in
> pg_stat_activity. I do not see any harm in this backend_type for
> slot-sync worker currently. This is on a similar line of connections
> used for logical-replications. And since a slot-sync worker also deals
> with wals-positions (lsns), it is okay to maintain backend_type as
> walsender unless you (or others) see any potential issue in doing
> that. So let me know.

I don't see any issue as well (though I understand it might
seems weird to see a walsender process being spawned doing non
replication stuff)

> 
>> ~~~
>> 4. primary_slot_name GUC value test:
>>
>> When standby is started with a non-existing primary_slot_name, the
>> wal-receiver gives an error but the slot-sync worker does not raise
>> any error/warning. It is no-op though as it has a check 'if
>> (XLogRecPtrIsInvalid(WalRcv->latestWalEnd)) do nothing'.   Is this
>> okay or shall the slot-sync worker too raise an error and exit?
>>
>> In another case, when standby is started with valid primary_slot_name,
>> but it is changed to some invalid value in runtime, then walreceiver
>> starts giving error but the slot-sync worker keeps on running. In this
>> case, unlike the previous case, it even did not go to no-op mode (as
>> it sees valid WalRcv->latestWalEnd from the earlier run) and keep
>> pinging primary repeatedly for slots.  Shall here it should error out
>> or at least be no-op until we give a valid primary_slot_name?
>>
> 

Nice catch, thanks!

> I reviewed it. There is no way to test the existence/validity of
> 'primary_slot_name' on standby without making a connection to primary.
> If primary_slot_name is invalid from the start, slot-sync worker will
> be no-op (as you tested) as WalRecv->latestWalENd will be invalid, and
> if 'primary_slot_name' is changed to invalid on runtime, slot-sync
> worker will still keep on pinging primary. But that should be okay (in
> fact needed) as it needs to sync at-least the previous slot's
> positions (in case it is delayed in doing so for some reason earlier).
> And once the slots are up-to-date on standby, even if worker pings
> primary, it will not see any change in slots lsns and thus go for
> longer nap. I think, it is not worth the effort to introduce the
> complexity of checking validity of 'primary_slot_name' on primary from
> standby for this rare scenario.
> 

Maybe another option could be to have the walreceiver a way to let the slot sync
worker knows that it (the walreceiver) was not able to start due to non existing
replication slot on the primary? (that way we'd avoid the slot sync worker having
to talk to the primary).

Not sure about the extra effort to make it works though.

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-12-05 05:08                           ` shveta malik <[email protected]>
  2023-12-05 08:48                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  0 siblings, 2 replies; 113+ messages in thread

From: shveta malik @ 2023-12-05 05:08 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Nisha Moond <[email protected]>; Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Mon, Dec 4, 2023 at 10:07 PM Drouvot, Bertrand
<[email protected]> wrote:
>
> Hi,
>
> On 12/4/23 6:10 AM, shveta malik wrote:
> > On Fri, Dec 1, 2023 at 5:40 PM Nisha Moond <[email protected]> wrote:
> >>
> >> Review for v41 patch.
> >
> > Thanks for the feedback.
> >
> >> ~~~
> >> 2.
> >> IIUC, the slotsyncworker's connection to the primary is to execute a
> >> query. Its aim is not walsender type connection, but at primary when
> >> queried, the 'backend_type' is set to 'walsender'.
> >> Snippet from primary db-
> >>
> >> datname  |   usename   | application_name | wait_event_type | backend_type
> >> ---------+-------------+------------------+-----------------+--------------
> >> postgres | replication | slotsyncworker   | Client          | walsender
> >>
> >> Is it okay?
> >>
> >
> > Slot sync worker uses 'libpqrcv_connect' for connection which sends
> > 'replication'-'database' key-value pair as one of the connection
> > options. And on the primary side, 'ProcessStartupPacket' on the basis
> > of this key-value pair sets the process as walsender one (am_walsender
> > = true).
> > And thus this reflects as backend_type='walsender' in
> > pg_stat_activity. I do not see any harm in this backend_type for
> > slot-sync worker currently. This is on a similar line of connections
> > used for logical-replications. And since a slot-sync worker also deals
> > with wals-positions (lsns), it is okay to maintain backend_type as
> > walsender unless you (or others) see any potential issue in doing
> > that. So let me know.
>
> I don't see any issue as well (though I understand it might
> seems weird to see a walsender process being spawned doing non
> replication stuff)
>
> >
> >> ~~~
> >> 4. primary_slot_name GUC value test:
> >>
> >> When standby is started with a non-existing primary_slot_name, the
> >> wal-receiver gives an error but the slot-sync worker does not raise
> >> any error/warning. It is no-op though as it has a check 'if
> >> (XLogRecPtrIsInvalid(WalRcv->latestWalEnd)) do nothing'.   Is this
> >> okay or shall the slot-sync worker too raise an error and exit?
> >>
> >> In another case, when standby is started with valid primary_slot_name,
> >> but it is changed to some invalid value in runtime, then walreceiver
> >> starts giving error but the slot-sync worker keeps on running. In this
> >> case, unlike the previous case, it even did not go to no-op mode (as
> >> it sees valid WalRcv->latestWalEnd from the earlier run) and keep
> >> pinging primary repeatedly for slots.  Shall here it should error out
> >> or at least be no-op until we give a valid primary_slot_name?
> >>
> >
>
> Nice catch, thanks!
>
> > I reviewed it. There is no way to test the existence/validity of
> > 'primary_slot_name' on standby without making a connection to primary.
> > If primary_slot_name is invalid from the start, slot-sync worker will
> > be no-op (as you tested) as WalRecv->latestWalENd will be invalid, and
> > if 'primary_slot_name' is changed to invalid on runtime, slot-sync
> > worker will still keep on pinging primary. But that should be okay (in
> > fact needed) as it needs to sync at-least the previous slot's
> > positions (in case it is delayed in doing so for some reason earlier).
> > And once the slots are up-to-date on standby, even if worker pings
> > primary, it will not see any change in slots lsns and thus go for
> > longer nap. I think, it is not worth the effort to introduce the
> > complexity of checking validity of 'primary_slot_name' on primary from
> > standby for this rare scenario.
> >
>
> Maybe another option could be to have the walreceiver a way to let the slot sync
> worker knows that it (the walreceiver) was not able to start due to non existing
> replication slot on the primary? (that way we'd avoid the slot sync worker having
> to talk to the primary).

Few points:
1) I think if we do it, we should do it in generic way i.e. slotsync
worker should go to no-op if walreceiver is not able to start due to
any reason and not only due to invalid primary_slot_name.
2) Secondly, slotsync worker needs to make sure it has synced the
slots so far i.e. worker should not go to no-op immediately on seeing
missing WalRcv process if there are pending slots to be synced.

So the generic way I see to have this optimization is:
1) Slotsync worker can use 'WalRcv->pid' to figure out if WalReceiver
is running or not.
2) Slotsync worker should check null 'WalRcv->pid' only when
no-activity is observed for threshold time i.e. it can do it during
existing logic of increasing naptime.
3) On finding null  'WalRcv->pid', worker can mark a flag to go to
no-op unless WalRcv->pid becomes valid again. Marking this flag during
increasing naptime will guarantee that the worker has taken all the
changes so far i.e. standby is not lagging in terms of slots.

Thoughts?

thanks
Shveta






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-05 08:48                             ` Drouvot, Bertrand <[email protected]>
  2023-12-05 10:29                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-12-05 08:48 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Nisha Moond <[email protected]>; Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 12/5/23 6:08 AM, shveta malik wrote:
> On Mon, Dec 4, 2023 at 10:07 PM Drouvot, Bertrand
> <[email protected]> wrote:
>> Maybe another option could be to have the walreceiver a way to let the slot sync
>> worker knows that it (the walreceiver) was not able to start due to non existing
>> replication slot on the primary? (that way we'd avoid the slot sync worker having
>> to talk to the primary).
> 
> Few points:
> 1) I think if we do it, we should do it in generic way i.e. slotsync
> worker should go to no-op if walreceiver is not able to start due to
> any reason and not only due to invalid primary_slot_name.

Agree.

> 2) Secondly, slotsync worker needs to make sure it has synced the
> slots so far i.e. worker should not go to no-op immediately on seeing
> missing WalRcv process if there are pending slots to be synced.

Agree.

> So the generic way I see to have this optimization is:
> 1) Slotsync worker can use 'WalRcv->pid' to figure out if WalReceiver
> is running or not.

Not sure that would work because the walreceiver keeps try re-starting
and so get a pid before reaching the "could not start WAL streaming: ERROR:  replication slot "XXXX" does not exist"
error.

We may want to add an extra check on walrcv->walRcvState (or should/could be enough by its own).
But walrcv->walRcvState is set to WALRCV_STREAMING way before walrcv_startstreaming().

Wouldn't that make sense to move it once we are sure that
walrcv_startstreaming() returns true and first_stream is true, here?

"
                         if (first_stream)
+                       {
                                 ereport(LOG,
                                                 (errmsg("started streaming WAL from primary at %X/%X on timeline %u",
                                                                 LSN_FORMAT_ARGS(startpoint), startpointTLI)));
+                               SpinLockAcquire(&walrcv->mutex);
+                               walrcv->walRcvState = WALRCV_STREAMING;
+                               SpinLockRelease(&walrcv->mutex);
+                       }
"

> 2) Slotsync worker should check null 'WalRcv->pid' only when
> no-activity is observed for threshold time i.e. it can do it during
> existing logic of increasing naptime.
> 3) On finding null  'WalRcv->pid', worker can mark a flag to go to
> no-op unless WalRcv->pid becomes valid again. Marking this flag during
> increasing naptime will guarantee that the worker has taken all the
> changes so far i.e. standby is not lagging in terms of slots.
> 

2) and 3) looks good to me but with a check on walrcv->walRcvState
looking for WALRCV_STREAMING state instead of looking for a non null
WalRcv->pid.

And only if it makes sense to move the walrcv->walRcvState = WALRCV_STREAMING as
mentioned above (I think it does).

Thoughts?

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 08:48                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-12-05 10:29                               ` shveta malik <[email protected]>
  2023-12-05 13:38                                 ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-05 10:29 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Nisha Moond <[email protected]>; Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Tue, Dec 5, 2023 at 2:18 PM Drouvot, Bertrand
<[email protected]> wrote:
>
> Hi,
>
> On 12/5/23 6:08 AM, shveta malik wrote:
> > On Mon, Dec 4, 2023 at 10:07 PM Drouvot, Bertrand
> > <[email protected]> wrote:
> >> Maybe another option could be to have the walreceiver a way to let the slot sync
> >> worker knows that it (the walreceiver) was not able to start due to non existing
> >> replication slot on the primary? (that way we'd avoid the slot sync worker having
> >> to talk to the primary).
> >
> > Few points:
> > 1) I think if we do it, we should do it in generic way i.e. slotsync
> > worker should go to no-op if walreceiver is not able to start due to
> > any reason and not only due to invalid primary_slot_name.
>
> Agree.
>
> > 2) Secondly, slotsync worker needs to make sure it has synced the
> > slots so far i.e. worker should not go to no-op immediately on seeing
> > missing WalRcv process if there are pending slots to be synced.
>
> Agree.
>
> > So the generic way I see to have this optimization is:
> > 1) Slotsync worker can use 'WalRcv->pid' to figure out if WalReceiver
> > is running or not.
>
> Not sure that would work because the walreceiver keeps try re-starting
> and so get a pid before reaching the "could not start WAL streaming: ERROR:  replication slot "XXXX" does not exist"
> error.
>

yes, right. pid will keep on toggling.

> We may want to add an extra check on walrcv->walRcvState (or should/could be enough by its own).
> But walrcv->walRcvState is set to WALRCV_STREAMING way before walrcv_startstreaming().
>

Agree. Check on 'walrcv->walRcvState' alone should suffice.

> Wouldn't that make sense to move it once we are sure that
> walrcv_startstreaming() returns true and first_stream is true, here?
>
> "
>                          if (first_stream)
> +                       {
>                                  ereport(LOG,
>                                                  (errmsg("started streaming WAL from primary at %X/%X on timeline %u",
>                                                                  LSN_FORMAT_ARGS(startpoint), startpointTLI)));
> +                               SpinLockAcquire(&walrcv->mutex);
> +                               walrcv->walRcvState = WALRCV_STREAMING;
> +                               SpinLockRelease(&walrcv->mutex);
> +                       }
> "
>

Yes, it makes sense and is the basis for current slot-sync worker
changes being discussed.

> > 2) Slotsync worker should check null 'WalRcv->pid' only when
> > no-activity is observed for threshold time i.e. it can do it during
> > existing logic of increasing naptime.
> > 3) On finding null  'WalRcv->pid', worker can mark a flag to go to
> > no-op unless WalRcv->pid becomes valid again. Marking this flag during
> > increasing naptime will guarantee that the worker has taken all the
> > changes so far i.e. standby is not lagging in terms of slots.
> >
>
> 2) and 3) looks good to me but with a check on walrcv->walRcvState
> looking for WALRCV_STREAMING state instead of looking for a non null
> WalRcv->pid.

yes. But I think, the worker should enter no-op, when walRcvState is
WALRCV_STOPPED and not when walRcvState != WALRCV_STREAMING as it is
okay to have WALRCV_WAITING/STARTING/RESTARTING. But the worker should
exit no-op only when it finds walRcvState switched back to
WALRCV_STREAMING.

>
> And only if it makes sense to move the walrcv->walRcvState = WALRCV_STREAMING as
> mentioned above (I think it does).
>

yes, I agree.

thanks
Shveta






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 08:48                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 10:29                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-05 13:38                                 ` Drouvot, Bertrand <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-12-05 13:38 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Nisha Moond <[email protected]>; Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 12/5/23 11:29 AM, shveta malik wrote:
> On Tue, Dec 5, 2023 at 2:18 PM Drouvot, Bertrand
> <[email protected]> wrote:
>> Wouldn't that make sense to move it once we are sure that
>> walrcv_startstreaming() returns true and first_stream is true, here?
>>
>> "
>>                           if (first_stream)
>> +                       {
>>                                   ereport(LOG,
>>                                                   (errmsg("started streaming WAL from primary at %X/%X on timeline %u",
>>                                                                   LSN_FORMAT_ARGS(startpoint), startpointTLI)));
>> +                               SpinLockAcquire(&walrcv->mutex);
>> +                               walrcv->walRcvState = WALRCV_STREAMING;
>> +                               SpinLockRelease(&walrcv->mutex);
>> +                       }
>> "
>>
> 
> Yes, it makes sense and is the basis for current slot-sync worker
> changes being discussed.

I think this change deserves its own dedicated thread and patch, does
that make sense?

If so, I'll submit one.

>>
>> 2) and 3) looks good to me but with a check on walrcv->walRcvState
>> looking for WALRCV_STREAMING state instead of looking for a non null
>> WalRcv->pid.
> 
> yes. But I think, the worker should enter no-op, when walRcvState is
> WALRCV_STOPPED and not when walRcvState != WALRCV_STREAMING as it is
> okay to have WALRCV_WAITING/STARTING/RESTARTING. But the worker should
> exit no-op only when it finds walRcvState switched back to
> WALRCV_STREAMING.
> 

Yeah, fully agree.

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-05 11:32                             ` Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Amit Kapila @ 2023-12-05 11:32 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Tue, Dec 5, 2023 at 10:38 AM shveta malik <[email protected]> wrote:
>
> On Mon, Dec 4, 2023 at 10:07 PM Drouvot, Bertrand
> <[email protected]> wrote:
> >
> > >
> > >> ~~~
> > >> 4. primary_slot_name GUC value test:
> > >>
> > >> When standby is started with a non-existing primary_slot_name, the
> > >> wal-receiver gives an error but the slot-sync worker does not raise
> > >> any error/warning. It is no-op though as it has a check 'if
> > >> (XLogRecPtrIsInvalid(WalRcv->latestWalEnd)) do nothing'.   Is this
> > >> okay or shall the slot-sync worker too raise an error and exit?
> > >>
> > >> In another case, when standby is started with valid primary_slot_name,
> > >> but it is changed to some invalid value in runtime, then walreceiver
> > >> starts giving error but the slot-sync worker keeps on running. In this
> > >> case, unlike the previous case, it even did not go to no-op mode (as
> > >> it sees valid WalRcv->latestWalEnd from the earlier run) and keep
> > >> pinging primary repeatedly for slots.  Shall here it should error out
> > >> or at least be no-op until we give a valid primary_slot_name?
> > >>
> > >
> >
> > Nice catch, thanks!
> >
> > > I reviewed it. There is no way to test the existence/validity of
> > > 'primary_slot_name' on standby without making a connection to primary.
> > > If primary_slot_name is invalid from the start, slot-sync worker will
> > > be no-op (as you tested) as WalRecv->latestWalENd will be invalid, and
> > > if 'primary_slot_name' is changed to invalid on runtime, slot-sync
> > > worker will still keep on pinging primary. But that should be okay (in
> > > fact needed) as it needs to sync at-least the previous slot's
> > > positions (in case it is delayed in doing so for some reason earlier).
> > > And once the slots are up-to-date on standby, even if worker pings
> > > primary, it will not see any change in slots lsns and thus go for
> > > longer nap. I think, it is not worth the effort to introduce the
> > > complexity of checking validity of 'primary_slot_name' on primary from
> > > standby for this rare scenario.
> > >
> >
> > Maybe another option could be to have the walreceiver a way to let the slot sync
> > worker knows that it (the walreceiver) was not able to start due to non existing
> > replication slot on the primary? (that way we'd avoid the slot sync worker having
> > to talk to the primary).
>
> Few points:
> 1) I think if we do it, we should do it in generic way i.e. slotsync
> worker should go to no-op if walreceiver is not able to start due to
> any reason and not only due to invalid primary_slot_name.
> 2) Secondly, slotsync worker needs to make sure it has synced the
> slots so far i.e. worker should not go to no-op immediately on seeing
> missing WalRcv process if there are pending slots to be synced.
>

Won't it be better to just ping and check the validity of
'primary_slot_name' at the start of slot-sync and if it is changed
anytime? I think it would be better to avoid adding dependency on
walreciever state as that sounds like needless complexity.

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-12-05 14:08                               ` Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-12-05 14:08 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; shveta malik <[email protected]>; +Cc: Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 12/5/23 12:32 PM, Amit Kapila wrote:
> On Tue, Dec 5, 2023 at 10:38 AM shveta malik <[email protected]> wrote:
>>
>> On Mon, Dec 4, 2023 at 10:07 PM Drouvot, Bertrand
>> <[email protected]> wrote:
>>>>
>>>
>>> Maybe another option could be to have the walreceiver a way to let the slot sync
>>> worker knows that it (the walreceiver) was not able to start due to non existing
>>> replication slot on the primary? (that way we'd avoid the slot sync worker having
>>> to talk to the primary).
>>
>> Few points:
>> 1) I think if we do it, we should do it in generic way i.e. slotsync
>> worker should go to no-op if walreceiver is not able to start due to
>> any reason and not only due to invalid primary_slot_name.
>> 2) Secondly, slotsync worker needs to make sure it has synced the
>> slots so far i.e. worker should not go to no-op immediately on seeing
>> missing WalRcv process if there are pending slots to be synced.
>>
> 
> Won't it be better to just ping and check the validity of
> 'primary_slot_name' at the start of slot-sync and if it is changed
> anytime? I think it would be better to avoid adding dependency on
> walreciever state as that sounds like needless complexity.

I think the overall extra complexity is linked to the fact that we first
want to ensure that the slots are in sync before shutting down the
sync slot worker.

I think than talking to the primary or relying on the walreceiver state
is "just" what would trigger the decision to shutdown the sync slot worker.

Relying on the walreceiver state looks better to me (as it avoids possibly
useless round trips with the primary).

Also the walreceiver could be down for multiple reasons, and I think there
is no point of having a sync slot worker running if the slots are in sync and
there is no walreceiver running (even if primary_slot_name is a valid one).

That said, I'm also ok with the "ping primary" approach if others have another
point of view and find it better.

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-12-06 05:26                                 ` Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Amit Kapila @ 2023-12-06 05:26 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: shveta malik <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Tue, Dec 5, 2023 at 7:38 PM Drouvot, Bertrand
<[email protected]> wrote:
>
> On 12/5/23 12:32 PM, Amit Kapila wrote:
> > On Tue, Dec 5, 2023 at 10:38 AM shveta malik <[email protected]> wrote:
> >>
> >> On Mon, Dec 4, 2023 at 10:07 PM Drouvot, Bertrand
> >> <[email protected]> wrote:
> >>>>
> >>>
> >>> Maybe another option could be to have the walreceiver a way to let the slot sync
> >>> worker knows that it (the walreceiver) was not able to start due to non existing
> >>> replication slot on the primary? (that way we'd avoid the slot sync worker having
> >>> to talk to the primary).
> >>
> >> Few points:
> >> 1) I think if we do it, we should do it in generic way i.e. slotsync
> >> worker should go to no-op if walreceiver is not able to start due to
> >> any reason and not only due to invalid primary_slot_name.
> >> 2) Secondly, slotsync worker needs to make sure it has synced the
> >> slots so far i.e. worker should not go to no-op immediately on seeing
> >> missing WalRcv process if there are pending slots to be synced.
> >>
> >
> > Won't it be better to just ping and check the validity of
> > 'primary_slot_name' at the start of slot-sync and if it is changed
> > anytime? I think it would be better to avoid adding dependency on
> > walreciever state as that sounds like needless complexity.
>
> I think the overall extra complexity is linked to the fact that we first
> want to ensure that the slots are in sync before shutting down the
> sync slot worker.
>
> I think than talking to the primary or relying on the walreceiver state
> is "just" what would trigger the decision to shutdown the sync slot worker.
>
> Relying on the walreceiver state looks better to me (as it avoids possibly
> useless round trips with the primary).
>

But the round trip will only be once in the beginning and if the user
changes the GUC primary-slot_name which shouldn't be that often.

> Also the walreceiver could be down for multiple reasons, and I think there
> is no point of having a sync slot worker running if the slots are in sync and
> there is no walreceiver running (even if primary_slot_name is a valid one).
>

I feel that is indirectly relying on the fact that the primary won't
advance logical slots unless physical standby has consumed data. Now,
it is possible that slot-sync worker lags behind and still needs to
sync more data for slots in which it makes sense for slot-sync worker
to be alive. I think we can try to avoid checking walreceiver status
till we can get more data to avoid the problem I mentioned but it
doesn't sound like a clean way to achieve our purpose.

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-12-06 06:18                                   ` shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-06 06:18 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Wed, Dec 6, 2023 at 10:56 AM Amit Kapila <[email protected]> wrote:
>
> On Tue, Dec 5, 2023 at 7:38 PM Drouvot, Bertrand
> <[email protected]> wrote:
> >
> > On 12/5/23 12:32 PM, Amit Kapila wrote:
> > > On Tue, Dec 5, 2023 at 10:38 AM shveta malik <[email protected]> wrote:
> > >>
> > >> On Mon, Dec 4, 2023 at 10:07 PM Drouvot, Bertrand
> > >> <[email protected]> wrote:
> > >>>>
> > >>>
> > >>> Maybe another option could be to have the walreceiver a way to let the slot sync
> > >>> worker knows that it (the walreceiver) was not able to start due to non existing
> > >>> replication slot on the primary? (that way we'd avoid the slot sync worker having
> > >>> to talk to the primary).
> > >>
> > >> Few points:
> > >> 1) I think if we do it, we should do it in generic way i.e. slotsync
> > >> worker should go to no-op if walreceiver is not able to start due to
> > >> any reason and not only due to invalid primary_slot_name.
> > >> 2) Secondly, slotsync worker needs to make sure it has synced the
> > >> slots so far i.e. worker should not go to no-op immediately on seeing
> > >> missing WalRcv process if there are pending slots to be synced.
> > >>
> > >
> > > Won't it be better to just ping and check the validity of
> > > 'primary_slot_name' at the start of slot-sync and if it is changed
> > > anytime? I think it would be better to avoid adding dependency on
> > > walreciever state as that sounds like needless complexity.
> >
> > I think the overall extra complexity is linked to the fact that we first
> > want to ensure that the slots are in sync before shutting down the
> > sync slot worker.
> >
> > I think than talking to the primary or relying on the walreceiver state
> > is "just" what would trigger the decision to shutdown the sync slot worker.
> >
> > Relying on the walreceiver state looks better to me (as it avoids possibly
> > useless round trips with the primary).
> >
>
> But the round trip will only be once in the beginning and if the user
> changes the GUC primary-slot_name which shouldn't be that often.
>
> > Also the walreceiver could be down for multiple reasons, and I think there
> > is no point of having a sync slot worker running if the slots are in sync and
> > there is no walreceiver running (even if primary_slot_name is a valid one).
> >
>
> I feel that is indirectly relying on the fact that the primary won't
> advance logical slots unless physical standby has consumed data.

Yes, that is the basis of this discussion. But now on rethinking, if
the user has not set 'standby_slot_names' on primary at first pace,
then even if walreceiver on standby is down, slots on primary will
keep on advancing and thus we need to sync. We have no check currently
that mandates users to set standby_slot_names.

> Now,
> it is possible that slot-sync worker lags behind and still needs to
> sync more data for slots in which it makes sense for slot-sync worker
> to be alive. I think we can try to avoid checking walreceiver status
> till we can get more data to avoid the problem I mentioned but it
> doesn't sound like a clean way to achieve our purpose.
>






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-06 09:30                                     ` Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-12-06 09:30 UTC (permalink / raw)
  To: shveta malik <[email protected]>; Amit Kapila <[email protected]>; +Cc: Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 12/6/23 7:18 AM, shveta malik wrote:
> On Wed, Dec 6, 2023 at 10:56 AM Amit Kapila <[email protected]> wrote:
>>
>> I feel that is indirectly relying on the fact that the primary won't
>> advance logical slots unless physical standby has consumed data.
> 
> Yes, that is the basis of this discussion.

Yes.

> But now on rethinking, if
> the user has not set 'standby_slot_names' on primary at first pace,
> then even if walreceiver on standby is down, slots on primary will
> keep on advancing

Oh right, good point.

> and thus we need to sync. 

Yes and I think our current check "XLogRecPtrIsInvalid(WalRcv->latestWalEnd)"
in synchronize_slots() prevents us to do so (as I think WalRcv->latestWalEnd
would be invalid for a non started walreceiver).

> We have no check currently
> that mandates users to set standby_slot_names.
> 

Yeah and OTOH unset standby_slot_names is currently the only
way for users to "force" advance failover slots if they want to (in case
say the standby is down for a long time and they don't want to block logical decoding
on the primary) as we don't provide a way to alter the failover property
(unless connecting with replication which sounds more like a hack).

>> Now,
>> it is possible that slot-sync worker lags behind and still needs to
>> sync more data for slots in which it makes sense for slot-sync worker
>> to be alive.

Right.

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-12-06 10:58                                       ` shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 07:49                                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  0 siblings, 2 replies; 113+ messages in thread

From: shveta malik @ 2023-12-06 10:58 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Amit Kapila <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Wed, Dec 6, 2023 at 3:00 PM Drouvot, Bertrand
<[email protected]> wrote:
>
> Hi,
>
> On 12/6/23 7:18 AM, shveta malik wrote:
> > On Wed, Dec 6, 2023 at 10:56 AM Amit Kapila <[email protected]> wrote:
> >>
> >> I feel that is indirectly relying on the fact that the primary won't
> >> advance logical slots unless physical standby has consumed data.
> >
> > Yes, that is the basis of this discussion.
>
> Yes.
>
> > But now on rethinking, if
> > the user has not set 'standby_slot_names' on primary at first pace,
> > then even if walreceiver on standby is down, slots on primary will
> > keep on advancing
>
> Oh right, good point.
>
> > and thus we need to sync.
>
> Yes and I think our current check "XLogRecPtrIsInvalid(WalRcv->latestWalEnd)"
> in synchronize_slots() prevents us to do so (as I think WalRcv->latestWalEnd
> would be invalid for a non started walreceiver).
>

But I think we do not need to deal with the case that walreceiver is
not started at all on standby. It is always started. Walreceiver not
getting started or down for long is a rare scenario. We have other
checks too for 'latestWalEnd' in slotsync worker and I think we should
retain those as is.

> > We have no check currently
> > that mandates users to set standby_slot_names.
> >
>
> Yeah and OTOH unset standby_slot_names is currently the only
> way for users to "force" advance failover slots if they want to (in case
> say the standby is down for a long time and they don't want to block logical decoding
> on the primary) as we don't provide a way to alter the failover property
> (unless connecting with replication which sounds more like a hack).
>

yes, right.

> >> Now,
> >> it is possible that slot-sync worker lags behind and still needs to
> >> sync more data for slots in which it makes sense for slot-sync worker
> >> to be alive.
>
> Right.
>
> Regards,
>
> --
> Bertrand Drouvot
> PostgreSQL Contributors Team
> RDS Open Source Databases
> Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-06 11:23                                         ` shveta malik <[email protected]>
  2023-12-07 07:53                                           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-07 08:03                                           ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-07 11:36                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-07 22:58                                           ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-10 11:03                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  1 sibling, 6 replies; 113+ messages in thread

From: shveta malik @ 2023-12-06 11:23 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Amit Kapila <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Wed, Dec 6, 2023 at 4:28 PM shveta malik <[email protected]> wrote:
>
> On Wed, Dec 6, 2023 at 3:00 PM Drouvot, Bertrand
> <[email protected]> wrote:
> >
> > Hi,
> >
> > On 12/6/23 7:18 AM, shveta malik wrote:
> > > On Wed, Dec 6, 2023 at 10:56 AM Amit Kapila <[email protected]> wrote:
> > >>
> > >> I feel that is indirectly relying on the fact that the primary won't
> > >> advance logical slots unless physical standby has consumed data.
> > >
> > > Yes, that is the basis of this discussion.
> >
> > Yes.
> >
> > > But now on rethinking, if
> > > the user has not set 'standby_slot_names' on primary at first pace,
> > > then even if walreceiver on standby is down, slots on primary will
> > > keep on advancing
> >
> > Oh right, good point.
> >
> > > and thus we need to sync.
> >
> > Yes and I think our current check "XLogRecPtrIsInvalid(WalRcv->latestWalEnd)"
> > in synchronize_slots() prevents us to do so (as I think WalRcv->latestWalEnd
> > would be invalid for a non started walreceiver).
> >
>
> But I think we do not need to deal with the case that walreceiver is
> not started at all on standby. It is always started. Walreceiver not
> getting started or down for long is a rare scenario. We have other
> checks too for 'latestWalEnd' in slotsync worker and I think we should
> retain those as is.
>
> > > We have no check currently
> > > that mandates users to set standby_slot_names.
> > >
> >
> > Yeah and OTOH unset standby_slot_names is currently the only
> > way for users to "force" advance failover slots if they want to (in case
> > say the standby is down for a long time and they don't want to block logical decoding
> > on the primary) as we don't provide a way to alter the failover property
> > (unless connecting with replication which sounds more like a hack).
> >
>
> yes, right.
>
> > >> Now,
> > >> it is possible that slot-sync worker lags behind and still needs to
> > >> sync more data for slots in which it makes sense for slot-sync worker
> > >> to be alive.
> >
> > Right.
> >
> > Regards,
> >
> > --
> > Bertrand Drouvot
> > PostgreSQL Contributors Team
> > RDS Open Source Databases
> > Amazon Web Services: https://aws.amazon.com


PFA v43, changes are:

v43-001:
1) Support of  'failover' dump in pg_dump. It was missing earlier.

v43-002:
1) Slot-sync worker now  checks validity of primary_slot_name by
connecting to primary, once during its start and later if
primary_slot_name GUC is changed.
2) Doc improvement (see logicaldecoding.sgml). More details on overall
slot-sync feature is added along with Nisha's comment of documenting
disabled-subscription behaviour wrt to synced slots.

thanks
Shveta


Attachments:

  [application/octet-stream] v43-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch (7.9K, ../../CAJpy0uBYJh+6vsX2VnecZvc=TUFMY93O9Pob07VzsQQV3cGBWQ@mail.gmail.com/2-v43-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch)
  download | inline diff:
From 5f3a4a0ca40f5d222596d384c4dd682fcc8a8614 Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Wed, 6 Dec 2023 15:58:21 +0530
Subject: [PATCH v43 3/3] Allow slot-sync worker to wait for the cascading
 standbys.

The GUC standby_slot_names is needed to be set on first standby
in order to allow it to wait for confirmation for cascading
standbys before updating logical 'synced' slots in slot-sync worker.
The intent is that the logical slots (synced ones) should not go
ahead of cascading standbys.

For the user created slots on first standby, we already have this wait
logic in place in logical walsender and in pg_logical_slot_get_changes_guts(),
but for synced slots (which can not be consumed yet), we need to make
sure that they are not going ahead of cascading standbys and that is
acheived by introducing the wait in slot-sync worker before we actually
update the slots.
---
 src/backend/replication/logical/slotsync.c | 89 ++++++++++++++++++++--
 src/backend/replication/walsender.c        |  4 +-
 src/include/replication/walsender.h        |  5 ++
 3 files changed, 88 insertions(+), 10 deletions(-)

diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 8266ebe537..a563b4bb25 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -108,7 +108,9 @@ static TimestampTz last_update_time;
  */
 #define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
 
-static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn);
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn,
+									  List **standby_slots);
+
 
 /*
  * Wait for remote slot to pass locally reserved position.
@@ -164,7 +166,7 @@ wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
 		CHECK_FOR_INTERRUPTS();
 
 		/* Handle any termination request if any */
-		ProcessSlotSyncInterrupts(wrconn);
+		ProcessSlotSyncInterrupts(wrconn, NULL /* standby_slots */ );
 
 		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
 
@@ -480,6 +482,52 @@ construct_slot_query(StringInfo s)
 					 " WHERE failover and sync_state != 'i'");
 }
 
+/*
+ * Wait for cascading physical standbys corresponding to physical slots
+ * specified in standby_slot_names GUC to confirm receiving given lsn.
+ */
+static void
+wait_for_standby_confirmation(XLogRecPtr wait_for_lsn,
+							  WalReceiverConn *wrconn)
+{
+	List	   *standby_slots;
+
+	/* Nothing to be done */
+	if (strcmp(standby_slot_names, "") == 0)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	for (;;)
+	{
+		int			rc;
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		/*
+		 * This will reload configuration and will refresh the standby_slots
+		 * as well provided standby_slot_names GUC is changed by the user.
+		 */
+		ProcessSlotSyncInterrupts(wrconn, &standby_slots);
+
+		/*
+		 * XXX: Is waiting for 5 second before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   5000L,
+					   WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+}
+
 /*
  * Synchronize single slot to given position.
  *
@@ -760,6 +808,7 @@ synchronize_slots(WalReceiverConn *wrconn)
 	List	   *remote_slot_list = NIL;
 	MemoryContext oldctx = CurrentMemoryContext;
 	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	XLogRecPtr	max_confirmed_lsn = 0;
 	ListCell   *cell;
 	bool		slot_updated = false;
 	TimestampTz now;
@@ -817,6 +866,9 @@ synchronize_slots(WalReceiverConn *wrconn)
 		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
 		Assert(!isnull);
 
+		if (remote_slot->confirmed_lsn > max_confirmed_lsn)
+			max_confirmed_lsn = remote_slot->confirmed_lsn;
+
 		/*
 		 * It is possible to get null values for LSN and Xmin if slot is
 		 * invalidated on the primary server, so handle accordingly.
@@ -860,6 +912,17 @@ synchronize_slots(WalReceiverConn *wrconn)
 	 */
 	drop_obsolete_slots(remote_slot_list);
 
+	/*
+	 * If there are cascading standbys, wait for their confirmation before we
+	 * update synced logical slots locally.
+	 *
+	 * Instead of waiting on confirmation for lsn of each slot, let us wait
+	 * once for confirmation on max_confirmed_lsn. If that is confirmed by
+	 * each cascading standby, we are good to update all the slots.
+	 */
+	if (remote_slot_list)
+		wait_for_standby_confirmation(max_confirmed_lsn, wrconn);
+
 	/* Now sync the slots locally */
 	foreach(cell, remote_slot_list)
 	{
@@ -1035,7 +1098,7 @@ validate_slotsync_parameters(char **dbname)
  * if validaity fails.
  */
 static void
-slotsync_reread_config(WalReceiverConn *wrconn)
+slotsync_reread_config(WalReceiverConn *wrconn, List **standby_slots)
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 	char	   *slotname = pstrdup(PrimarySlotName);
@@ -1049,8 +1112,14 @@ slotsync_reread_config(WalReceiverConn *wrconn)
 	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
 	Assert(dbname);
 
-	ConfigReloadPending = false;
-	ProcessConfigFile(PGC_SIGHUP);
+	/*
+	 * Reload configs and recreate the standby_slot_names_list if GUC
+	 * standby_slot_names changed.
+	 */
+	if (standby_slots)
+		WalSndRereadConfigAndReInitSlotList(standby_slots);
+	else
+		ProcessConfigFile(PGC_SIGHUP);
 
 	conninfoChanged = strcmp(conninfo, PrimaryConnInfo) != 0;
 	slotnameChanged = strcmp(slotname, PrimarySlotName) != 0;
@@ -1093,7 +1162,8 @@ slotsync_reread_config(WalReceiverConn *wrconn)
  * Interrupt handler for main loop of slot sync worker.
  */
 static void
-ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn,
+						  List **standby_slots)
 {
 	CHECK_FOR_INTERRUPTS();
 
@@ -1109,7 +1179,10 @@ ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
 
 
 	if (ConfigReloadPending)
-		slotsync_reread_config(wrconn);
+	{
+		ConfigReloadPending = false;
+		slotsync_reread_config(wrconn, standby_slots);
+	}
 }
 
 /*
@@ -1183,7 +1256,7 @@ ReplSlotSyncWorkerMain(Datum main_arg)
 		int			rc;
 		long		naptime;
 
-		ProcessSlotSyncInterrupts(wrconn);
+		ProcessSlotSyncInterrupts(wrconn, NULL /* standby_slots */ );
 
 		naptime = synchronize_slots(wrconn);
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 7b6962f354..537b5e27dc 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1609,7 +1609,7 @@ PhysicalWakeupLogicalWalSnd(void)
  * Reload the config file and reinitialize the standby slot list if the GUC
  * standby_slot_names has changed.
  */
-static void
+void
 WalSndRereadConfigAndReInitSlotList(List **standby_slots)
 {
 	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
@@ -1634,7 +1634,7 @@ WalSndRereadConfigAndReInitSlotList(List **standby_slots)
  * it removes slots that have been invalidated, dropped, or converted to
  * logical slots.
  */
-static void
+void
 WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 {
 	ListCell   *lc;
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index ecd212c7b6..07b2c719ae 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -13,6 +13,7 @@
 #define _WALSENDER_H
 
 #include "access/xlogdefs.h"
+#include "nodes/pg_list.h"
 
 /*
  * What to do with a snapshot in create replication slot command.
@@ -48,7 +49,11 @@ extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn,
+									 List **standby_slots);
 extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
+extern List *WalSndGetStandbySlots(void);
+extern void WalSndRereadConfigAndReInitSlotList(List **standby_slots);
 
 /*
  * Remember that we want to wakeup walsenders later
-- 
2.34.1



  [application/octet-stream] v43-0002-Add-logical-slot-sync-capability-to-the-physical.patch (83.4K, ../../CAJpy0uBYJh+6vsX2VnecZvc=TUFMY93O9Pob07VzsQQV3cGBWQ@mail.gmail.com/3-v43-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From c50b531b19c95d161d8cfb66b8ff0e7bea0c59a1 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Mon, 27 Nov 2023 16:47:02 +0800
Subject: [PATCH v43 2/3] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover. All the failover logical
replication slots on the primary (assuming configurations are
appropriate) are automatically created on the physical standbys and
are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The nap time of worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. And if
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated. If a logical slot on the primary is valid but is invalidated
on the standby due to conflict (say required rows removed on the primary),
then that slot is dropped and recreated on the standby in next sync-cycle.
It is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently).

Slots synced on the standby can be identified using 'sync_state' column of
pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but waiting for the remote slot on the
     primary server to catch up.
'r': ready for periodic syncs.
---
 doc/src/sgml/bgworker.sgml                    |   16 +-
 doc/src/sgml/config.sgml                      |   35 +-
 doc/src/sgml/logicaldecoding.sgml             |   34 +
 doc/src/sgml/system-views.sgml                |   25 +
 src/backend/access/transam/xlogrecovery.c     |   10 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    4 +
 src/backend/postmaster/postmaster.c           |    6 +
 .../libpqwalreceiver/libpqwalreceiver.c       |   44 +-
 src/backend/replication/logical/Makefile      |    1 +
 src/backend/replication/logical/logical.c     |   20 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1321 +++++++++++++++++
 src/backend/replication/logical/tablesync.c   |    1 +
 src/backend/replication/slot.c                |   22 +-
 src/backend/replication/slotfuncs.c           |   37 +-
 src/backend/replication/walsender.c           |    6 +-
 src/backend/storage/ipc/ipci.c                |    2 +
 src/backend/tcop/postgres.c                   |   12 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   10 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/commands/subscriptioncmds.h       |    4 +
 src/include/postmaster/bgworker.h             |    1 +
 src/include/replication/slot.h                |   22 +-
 src/include/replication/walreceiver.h         |   19 +
 src/include/replication/worker_internal.h     |   12 +
 src/test/recovery/t/050_verify_slot_order.pl  |  127 ++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    2 +
 32 files changed, 1788 insertions(+), 30 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2c393385a9..377f301b63 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -121,11 +121,17 @@ typedef struct BackgroundWorker
    <literal>BgWorkerStart_ConsistentState</literal> (start as soon as a consistent state
    has been reached in a hot standby, allowing processes to connect to
    databases and run read-only queries), and
-   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system has
-   entered normal read-write state).  Note the last two values are equivalent
-   in a server that's not a hot standby.  Note that this setting only indicates
-   when the processes are to be started; they do not stop when a different state
-   is reached.
+   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system
+   has entered normal read-write state. Note that the
+   <literal>BgWorkerStart_ConsistentState</literal> and
+   <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
+   in a server that's not a hot standby), and
+   <literal>BgWorkerStart_ConsistentState_HotStandby</literal> (same meaning as
+   <literal>BgWorkerStart_ConsistentState</literal> but it is more strict in
+   terms of the server i.e. start the  worker only if it is hot-standby; if it is
+   consistent state in non-standby, worker will not be started). Note that this
+   setting only indicates when the processes are to be started; they do not stop
+   when a different state is reached.
   </para>
 
   <para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 30d9b53e03..1977962caf 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4373,6 +4373,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must enable
+        <varname>enable_syncslot</varname> for the standbys to receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4566,10 +4572,15 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           A password needs to be provided too, if the sender demands password
           authentication.  It can be provided in the
           <varname>primary_conninfo</varname> string, or in a separate
-          <filename>~/.pgpass</filename> file on the standby server (use
+          <filename>~/.pgpass</filename> file on the standby server. (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          Specify <literal>dbname</literal> in
+          <varname>primary_conninfo</varname> string to allow synchronization
+          of slots from the primary server to the standby server.
+          This will only be used for slot synchronization. It is ignored
+          for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4894,6 +4905,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..0defa0ec2b 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,40 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and set
+     <varname>enable_syncslot</varname> on the standby. For the synchronization
+     to work, it is mandatory to have physical replication slot between the
+     primary and the standby. This physical replication slot for the standby
+     should be listed in <varname>standby_slot_names</varname> on the primary
+     to prevent the subscriber from consuming changes faster than the hot
+     standby. Additionally, similar to creating a logical replication slot
+     on the hot standby, <varname>hot_standby_feedback</varname> should be
+     set on the standby and a physical slot between the primary and the standby
+     should be used.
+    </para>
+
+    <para>
+     By enabling synchronization of slots, logical replication can be resumed
+     after failover depending upon the
+     <link linkend="view-pg-replication-slots">pg_replication_slots</link>.<structfield>sync_state</structfield>
+     for the synchronized slots on the standby at the time of failover.
+     The slots which were in ready sync_state ('r') on the standby before
+     failover can be used for logical replication after failover. However,
+     the slots which were in initiated sync_state ('i) and were not
+     sync-ready ('r') at the time of failover will be dropped and logical
+     replication for such slots can not be resumed after failover. This applies
+     to the case where a logical subscription is disabled before failover and is
+     enabled after failover. If the synchronized slot due to disabled
+     subscription could not be made sync-ready ('r') on standby, then the
+     subscription can not be resumed after failover even when enabled.
+     If the primary is idle, making the synchronized slot on the standby
+     as sync-ready ('r') for enabled subscription may take noticeable time.
+     This can be sped up by calling the
+     <function>pg_log_standby_snapshot</function> function on the primary.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..3f6e2f82c8 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,31 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has enabled slots synchronization.
+      </para>
+      <para>
+       State code:
+       <literal>n</literal> = none for user created slots,
+       <literal>i</literal> = sync initiated for the slot but slot is not ready
+        yet for periodic syncs,
+       <literal>r</literal> = ready for periodic syncs.
+      </para>
+      <para>
+      The hot standby can have any of these sync_state for the slots but on a
+      hot standby, the slots with state 'r' and 'i' can neither be used for logical
+      decoded nor dropped by the user. The primary server will have sync_state
+      as 'n' for all the slots. But if the standby is promoted to become the
+      new primary server, sync_state can be seen 'r' as well. On this new
+      primary server, slots with sync_state as 'r' and 'n' will behave the same.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c61566666a..9fb09ba0fc 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1436,15 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index c345639086..854c967910 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -22,6 +22,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "replication/worker_internal.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -130,6 +131,9 @@ static const struct
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index ae31d66930..d75bb378b7 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -117,6 +117,7 @@
 #include "postmaster/syslogger.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -1006,6 +1007,8 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	SlotSyncWorkerRegister();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
@@ -5743,6 +5746,9 @@ bgworker_should_start_now(BgWorkerStartTime start_time)
 		case PM_HOT_STANDBY:
 			if (start_time == BgWorkerStart_ConsistentState)
 				return true;
+			else if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
+					 pmState != PM_RUN)
+				return true;
 			/* fall through */
 
 		case PM_RECOVERY:
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 4fadd3df7a..7e1492f046 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -418,6 +421,45 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	PQconninfoOption *opt;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
@@ -997,7 +1039,7 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
  */
 static void
 libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
-					 bool failover)
+					bool failover)
 {
 	StringInfoData cmd;
 	PGresult   *res;
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..7de68a61e2 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,26 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	/*
+	 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+	 * promotion.
+	 */
+	if (!RecoveryInProgress() && slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+		elog(ERROR, "replication slot \"%s\" was not synced completely from the primary server",
+			 NameStr(slot->data.name));
+
+	/*
+	 * Do not allow consumption of a "synchronized" slot until the standby
+	 * gets promoted.
+	 */
+	if (RecoveryInProgress() && slot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot use replication slot \"%s\" for logical decoding",
+						NameStr(slot->data.name)),
+				 errdetail("This slot is being synced from the primary server."),
+				 errhint("Specify another replication slot.")));
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..8266ebe537
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1321 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * It also takes care of dropping the slots which were created by it and are
+ * currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/table.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_database.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/*
+ * Struct for sharing information between startup process and slot
+ * sync worker.
+ *
+ * Slot sync worker's pid is needed by startup process in order to
+ * shut it down during promotion.
+ */
+typedef struct SlotSyncWorkerCtx
+{
+	pid_t		pid;
+	slock_t		mutex;
+} SlotSyncWorkerCtx;
+
+SlotSyncWorkerCtx *SlotSyncWorker = NULL;
+
+/* GUC variable */
+bool		enable_syncslot = false;
+
+/* The last sync-cycle time when the worker updated any of the slots. */
+static TimestampTz last_update_time;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+/*
+ * Number of attempts for wait_for_primary_slot_catchup() after
+ * which it aborts the wait and the slot sync worker then moves
+ * to the next slot creation/sync.
+ */
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
+
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn);
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * *persist will be set to false if the slot has disappeared or was invalidated
+ * on the primary; otherwise, it will be set to true.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *persist)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+	if (persist)
+		*persist = true;
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn, confirmed_flush_lsn,"
+					 " catalog_xmin FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		XLogRecPtr	new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *slot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Handle any termination request if any */
+		ProcessSlotSyncInterrupts(wrconn);
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot info for slot \"%s\" from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" disappeared from the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(slot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" invalidated on the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			ExecClearTuple(slot);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * Once we got valid restart_lsn, then confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(slot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\"",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up.
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			slots = lappend(slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(slots);
+}
+
+/*
+ * Get list of local logical slot names which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slot_names(void)
+{
+	List	   *localSyncedSlots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			localSyncedSlots = lappend(localSyncedSlots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return localSyncedSlots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+check_sync_slot_validity(ReplicationSlot *local_slot, List *remote_slots,
+						 bool *locally_invalidated)
+{
+	ListCell   *cell;
+
+	foreach(cell, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			SpinLockAcquire(&local_slot->mutex);
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+			SpinLockRelease(&local_slot->mutex);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Also drop the slots that are valid on the primary that got invalidated
+ * on the standby due to conflict (say required rows removed on the primary).
+ * The assumption is, that these will get recreated in next sync-cycle and
+ * it is okay to drop and recreate such slots as long as these are not
+ * consumable on the standby (which is the case currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slot_list = NIL;
+	ListCell   *lc_slot;
+
+	/*
+	 * Get the list of local 'synced' slot so that those not on remote could
+	 * be dropped.
+	 */
+	local_slot_list = get_local_synced_slot_names();
+
+	foreach(lc_slot, local_slot_list)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc_slot);
+		bool		local_exists = false;
+		bool		locally_invalidated = false;
+
+		local_exists = check_sync_slot_validity(local_slot, remote_slot_list,
+												&locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!local_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Constructs the query in order to get failover logical slots
+ * information from the primary server.
+ */
+static void
+construct_slot_query(StringInfo s)
+{
+	/*
+	 * Fetch slots with failover enabled.
+	 *
+	 * If we are on cascading standby, we should fetch only those slots from
+	 * the first standby which have sync_state as either 'n' or 'r'. Slots
+	 * with sync_state as 'i' are not sync ready yet. And when we are on the
+	 * first standby, the primary server is supposed to have slots with
+	 * sync_state as 'n' only (or it may have all 'n', 'r' and 'i' if standby
+	 * is promoted as primary). Thus in all the cases, filter sync_state !='i'
+	 * is appropriate one.
+	 */
+	appendStringInfo(s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover and sync_state != 'i'");
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ */
+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+					 bool *slot_updated)
+{
+	ReplicationSlot *s;
+	char		sync_state = 0;
+
+	/*
+	 * Make sure that concerned WAL is received before syncing slot to target
+	 * lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* Search for the named slot */
+	if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		SpinLockAcquire(&s->mutex);
+		sync_state = s->data.sync_state;
+		SpinLockRelease(&s->mutex);
+	}
+
+	StartTransactionCommand();
+
+	/*
+	 * Already existing slot (created by slot sync worker) and ready for sync,
+	 * acquire and sync it.
+	 */
+	if (sync_state == SYNCSLOT_STATE_READY)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&MyReplicationSlot->mutex);
+			MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&MyReplicationSlot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * With hot_standby_feedback enabled and invalidations handled
+		 * apropriately as above, this should never happen.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn)
+		{
+			ereport(ERROR,
+					errmsg("not synchronizing local slot \"%s\" LSN(%X/%X)"
+						   " to remote slot's LSN(%X/%X) as synchronization "
+						   " would move it backwards", remote_slot->name,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   LSN_FORMAT_ARGS(remote_slot->restart_lsn)));
+
+			goto cleanup;
+		}
+
+		if (remote_slot->confirmed_lsn != MyReplicationSlot->data.confirmed_flush ||
+			remote_slot->restart_lsn != MyReplicationSlot->data.restart_lsn ||
+			remote_slot->catalog_xmin != MyReplicationSlot->data.catalog_xmin)
+		{
+			/* Update LSN of slot to remote slot's current position */
+			local_slot_update(remote_slot);
+			ReplicationSlotSave();
+			*slot_updated = true;
+		}
+	}
+
+	/*
+	 * Already existing slot but not ready (i.e. waiting for the primary
+	 * server to catch-up), lets attempt to make it sync-ready now.
+	 */
+	else if (sync_state == SYNCSLOT_STATE_INITIATED)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&MyReplicationSlot->mutex);
+			MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&MyReplicationSlot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * Refer the slot creation part (last 'else' block) for more details
+		 * on this wait.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+			{
+				goto cleanup;
+			}
+		}
+
+		/*
+		 * Wait for primary is over, update the lsns and mark the slot as
+		 * READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Save the changes */
+		ReplicationSlotMarkDirty();
+		ReplicationSlotSave();
+
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready "
+							"now", remote_slot->name));
+	}
+	/* User created slot with the same name exists, raise ERROR. */
+	else if (sync_state == SYNCSLOT_STATE_NONE)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("skipping sync of slot \"%s\" as it is a user created"
+						" slot", remote_slot->name),
+				 errdetail("This slot has failover enabled on the primary and"
+						   " thus is sync candidate but user created slot with"
+						   " the same name already exists on the standby")));
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+		ReplicationSlot *slot;
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover,
+							  SYNCSLOT_STATE_INITIATED);
+
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * If the local restart_lsn and/or local catalog_xmin is ahead of
+		 * those on the remote then we cannot create the local slot in sync
+		 * with the primary server because that would mean moving the local
+		 * slot backwards and we might not have WALs retained for old LSN. In
+		 * this case we will wait for the primary server's restart_lsn and
+		 * catalog_xmin to catch up with the local one before attempting the
+		 * sync.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			bool		persist;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &persist))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid) and attempt the wait and synchronization in
+				 * the next cycle.
+				 */
+				if (persist)
+				{
+					ReplicationSlotPersist();
+					*slot_updated = true;
+				}
+
+				goto cleanup;
+			}
+		}
+
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready "
+							"now", remote_slot->name));
+	}
+
+cleanup:
+
+	ReplicationSlotRelease();
+	CommitTransactionCommand();
+
+	return;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and update
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns nap time for the next sync-cycle.
+ */
+static long
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *slot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	MemoryContext oldctx = CurrentMemoryContext;
+	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	ListCell   *cell;
+	bool		slot_updated = false;
+	TimestampTz now;
+
+	/* The primary_slot_name is not set yet or WALs not received yet */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		return naptime;
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* The syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	/*
+	 * Make result tuples live outside TopTransactionContext to make them
+	 * accessible even after transaction is committed.
+	 */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct query to get slots info from the primary server */
+	initStringInfo(&s);
+	construct_slot_query(&s);
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(slot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(slot, 5,
+																	   &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(slot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(slot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(slot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(slot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(slot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(cell, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		synchronize_one_slot(wrconn, remote_slot, &slot_updated);
+	}
+
+	now = GetCurrentTimestamp();
+
+	/*
+	 * If any of the slots get updated in this sync-cycle, retain default
+	 * naptime and update 'last_update_time' in slot sync worker. But if no
+	 * activity is observed in this sync-cycle, then increase naptime provided
+	 * inactivity time reaches threshold.
+	 */
+	if (slot_updated)
+		last_update_time = now;
+
+	else if (TimestampDifferenceExceeds(last_update_time,
+										now, WORKER_INACTIVITY_THRESHOLD_MS))
+		naptime = WORKER_INACTIVITY_NAPTIME_MS;
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	return naptime;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker",
+							&err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Connects primary to validate the slot specified in primary_slot_name.
+ *
+ * Exits the worker if physical slot with the specified name does not exist.
+ */
+static void
+validate_primary_slot(WalReceiverConn *wrconn)
+{
+	WalRcvExecResult *res;
+	Oid			slotRow[1] = {BOOLOID};
+	StringInfoData cmd;
+	bool		isnull;
+	TupleTableSlot *slot;
+	bool		valid;
+	bool		tuple_ok;
+
+	/* Syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "select count(*) = 1 from pg_replication_slots where "
+					 "slot_type='physical' and slot_name=%s",
+					 quote_literal_cstr(PrimarySlotName));
+
+	res = walrcv_exec(wrconn, cmd.data, 1, slotRow);
+	pfree(cmd.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch primary_slot_name info from the "
+						"primary: %s", res->err)));
+
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	tuple_ok = tuplestore_gettupleslot(res->tuplestore, true, false, slot);
+	Assert(tuple_ok);			/* It must return one tuple */
+
+	valid = DatumGetBool(slot_getattr(slot, 1, &isnull));
+	Assert(!isnull);
+
+	if (!valid)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as slot specified in "
+					   "primary_slot_name is not valid"));
+
+	ExecClearTuple(slot);
+	walrcv_clear_result(res);
+	CommitTransactionCommand();
+}
+
+/*
+ * Checks if GUCs are set appropriately before starting slot sync worker
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+	/*
+	 * Since 'enable_syncslot' is ON, check that other GUC settings
+	 * (primary_slot_name, hot_standby_feedback, wal_level, primary_conninfo)
+	 * are compatible with slot synchronization. If not, raise ERROR.
+	 */
+
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as primary_slot_name is "
+					   "not set"));
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as hot_standby_feedback "
+					   "is off"));
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+				errmsg("exiting slots synchronisation as it requires "
+					   "wal_level >= logical"));
+
+	/*
+	 * The primary_conninfo is required to make connection to primary for
+	 * getting slots information.
+	 */
+	if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as primary_conninfo "
+					   "is not set"));
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	*dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (*dbname == NULL)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as dbname is not "
+					   "specified in primary_conninfo"));
+
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs changed, validate the values again
+ * through validate_slotsync_parameters() which will exit the worker
+ * if validaity fails.
+ */
+static void
+slotsync_reread_config(WalReceiverConn *wrconn)
+{
+	char	   *conninfo = pstrdup(PrimaryConnInfo);
+	char	   *slotname = pstrdup(PrimarySlotName);
+	bool		syncslot = enable_syncslot;
+	bool		standbyfeedback = hot_standby_feedback;
+	bool		revalidate = false;
+	char	   *dbname;
+	bool		conninfoChanged;
+	bool		slotnameChanged;
+
+	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	Assert(dbname);
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	conninfoChanged = strcmp(conninfo, PrimaryConnInfo) != 0;
+	slotnameChanged = strcmp(slotname, PrimarySlotName) != 0;
+
+	if (conninfoChanged || slotnameChanged ||
+		(syncslot != enable_syncslot) ||
+		(standbyfeedback != hot_standby_feedback))
+	{
+		revalidate = true;
+	}
+
+	pfree(conninfo);
+	pfree(slotname);
+
+	if (revalidate)
+	{
+		char	   *new_dbname;
+
+		validate_slotsync_parameters(&new_dbname);
+
+		/*
+		 * Since we have initialized this worker with old dbname, thus exit if
+		 * dbname changed. Let it get restarted and connect to new dbname
+		 * specified.
+		 */
+		if (conninfoChanged && strcmp(dbname, new_dbname) != 0)
+		{
+			ereport(ERROR,
+					errmsg("exiting slot sync woker as dbname in "
+						   "primary_conninfo changed"));
+		}
+
+		if (slotnameChanged)
+			validate_primary_slot(wrconn);
+	}
+}
+
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting"
+					   " down on receiving SIGINT"));
+
+		walrcv_disconnect(wrconn);
+		proc_exit(0);
+	}
+
+
+	if (ConfigReloadPending)
+		slotsync_reread_config(wrconn);
+}
+
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = 0;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	Assert(SlotSyncWorker->pid == 0);
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	validate_slotsync_parameters(&dbname);
+
+	/*
+	 * Connect to the database specified by user in primary_conninfo. We need
+	 * a database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/*
+	 * Connect to primary and validate the slot specified in
+	 * primary_slot_name.
+	 */
+	validate_primary_slot(wrconn);
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime;
+
+		ProcessSlotSyncInterrupts(wrconn);
+
+		naptime = synchronize_slots(wrconn);
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsSlotSyncWorker(void)
+{
+	return SlotSyncWorker->pid == MyProcPid;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (!SlotSyncWorker->pid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Wait for it to die. */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long. */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (!SlotSyncWorker->pid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Allocate and initialize slow sync worker shared memory
+ */
+void
+SlotSyncWorkerShmemInit(void)
+{
+	Size		size;
+	bool		found;
+
+	size = sizeof(SlotSyncWorkerCtx);
+	size = MAXALIGN(size);
+
+	SlotSyncWorker = (SlotSyncWorkerCtx *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
+
+	if (!found)
+	{
+		memset(SlotSyncWorker, 0, size);
+		SpinLockInit(&SlotSyncWorker->mutex);
+	}
+}
+
+/*
+ * Register the background worker for slots synchronization provided
+ * enable_syncslot is ON.
+ */
+void
+SlotSyncWorkerRegister(void)
+{
+	BackgroundWorker bgw;
+
+	if (!enable_syncslot)
+	{
+		ereport(LOG,
+				errmsg("skipping slots synchronization as enable_syncslot is "
+					   "disabled."));
+		return;
+	}
+
+	memset(&bgw, 0, sizeof(bgw));
+
+	/* We need database connection which needs shared-memory access as well. */
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+
+	/* Start as soon as a consistent state has been reached in a hot standby */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState_HotStandby;
+
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "replication slot sync worker");
+	snprintf(bgw.bgw_type, BGW_MAXLEN,
+			 "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
+	bgw.bgw_notify_pid = 0;
+	bgw.bgw_main_arg = (Datum) 0;
+
+	RegisterBackgroundWorker(&bgw);
+}
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index d2d8bf1a7a..f1675dbd85 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "commands/subscriptioncmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index d35e6dec55..eac8fe2fe8 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -112,7 +113,7 @@ int			max_replication_slots = 10; /* the maximum number of replication
 char	   *standby_slot_names;
 
 /* This is parsed and cached list for raw standby_slot_names. */
-static List	   *standby_slot_names_list = NIL;
+static List *standby_slot_names_list = NIL;
 
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
@@ -264,7 +265,7 @@ ReplicationSlotValidateName(const char *name, int elevel)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover)
+					  bool two_phase, bool failover, char sync_state)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -325,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = sync_state;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -684,12 +686,26 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+	{
+		ReplicationSlotRelease();
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary.")));
+	}
+
 	ReplicationSlotDropAcquired();
 }
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index fb6e37d2c3..79b9ca5704 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -44,7 +44,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false);
+						  false, SYNCSLOT_STATE_NONE);
 
 	if (immediately_reserve)
 	{
@@ -137,7 +137,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover);
+						  failover, SYNCSLOT_STATE_NONE);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 58e298af89..7b6962f354 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1071,7 +1071,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false);
+							  false, false, SYNCSLOT_STATE_NONE);
 
 		if (reserve_wal)
 		{
@@ -1102,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover);
+							  two_phase, failover, SYNCSLOT_STATE_NONE);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, true);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2225a4a6e6..76a0f9be58 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -36,6 +36,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -336,6 +337,7 @@ CreateOrAttachShmemStructs(void)
 	WalRcvShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7298a187d1..92ea349488 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -60,6 +60,7 @@
 #include "replication/logicalworker.h"
 #include "replication/slot.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
@@ -3286,6 +3287,17 @@ ProcessInterrupts(void)
 			 */
 			proc_exit(1);
 		}
+		else if (IsSlotSyncWorker())
+		{
+			ereport(DEBUG1,
+					(errmsg_internal("replication slot sync worker is shutting down due to administrator command")));
+
+			/*
+			 * Slot sync worker can be stopped at any time.
+			 * Use exit status 1 so the background worker is restarted.
+			 */
+			proc_exit(1);
+		}
 		else if (IsBackgroundWorker)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 4b776266a4..2b40d5db6e 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2021,6 +2022,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_POSTMASTER, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5d940b72cd..39224137e1 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -358,6 +358,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = off			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d906734750..6a8192ad83 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 214dc6c29e..75b4b2040d 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -17,6 +17,7 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"
 
 extern ObjectAddress CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										bool isTopLevel);
@@ -28,4 +29,7 @@ extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
 extern char defGetStreamingMode(DefElem *def);
 
+extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn,
+										char *slotname, bool missing_ok);
+
 #endif							/* SUBSCRIPTIONCMDS_H */
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index e90ff376a6..8559900b70 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -79,6 +79,7 @@ typedef enum
 	BgWorkerStart_PostmasterStart,
 	BgWorkerStart_ConsistentState,
 	BgWorkerStart_RecoveryFinished,
+	BgWorkerStart_ConsistentState_HotStandby,
 } BgWorkerStartTime;
 
 #define BGW_DEFAULT_RESTART_INTERVAL	60
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index ca06e5b1ad..3cfcfef638 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -15,7 +15,6 @@
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "storage/spin.h"
-#include "replication/walreceiver.h"
 
 /*
  * Behaviour of replication slots, upon release or crash.
@@ -52,6 +51,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +119,13 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Is this a slot created by a sync-slot worker?
+	 *
+	 * Relevant for logical slots on the physical standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)?
 	 * Only relevant for logical slots on the primary server.
@@ -225,9 +239,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover);
+								  bool two_phase, bool failover,
+								  char sync_state);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
@@ -253,7 +268,6 @@ extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_l
 extern int	ReplicationSlotIndex(ReplicationSlot *slot);
 extern bool ReplicationSlotName(int index, Name name);
 extern void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot);
-extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok);
 
 extern void StartupReplicationSlots(void);
 extern void CheckPointReplicationSlots(bool is_shutdown);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index d5bca2500e..8a70965f17 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -19,6 +19,7 @@
 #include "pgtime.h"
 #include "port/atomics.h"
 #include "replication/logicalproto.h"
+#include "replication/slot.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/latch.h"
@@ -279,6 +280,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -403,6 +419,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -428,6 +445,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 84bb79ac0f..16b1129fcd 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -237,6 +237,11 @@ extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
+/* Slot sync worker objects */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+extern PGDLLIMPORT bool enable_syncslot;
+
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
@@ -326,6 +331,13 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
+extern void SlotSyncWorkerRegister(void);
+extern void ShutDownSlotSync(void);
+extern void slotsync_drop_initiated_slots(void);
+extern bool IsSlotSyncWorker(void);
+extern void SlotSyncWorkerShmemInit(void);
+
 #define isParallelApplyWorker(worker) ((worker)->in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
 #define isTablesyncWorker(worker) ((worker)->in_use && \
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
index bff0f52a46..e7a00bde12 100644
--- a/src/test/recovery/t/050_verify_slot_order.pl
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -146,4 +146,131 @@ $result = $subscriber1->safe_psql('postgres',
 	"SELECT count(*) = $primary_row_count FROM tab_int;");
 is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
 
+# Test logical failover slots on the standby
+# Configure standby3 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub1_slot->| ----> subscriber1 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb3_slot--->| ----> standby3 (connected via streaming replication)
+#                                        |                 lsub1_slot(synced_slot)
+
+# Cleanup old standby_slot_names
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = ''
+));
+$primary->start;
+
+$primary->psql('postgres',
+		q{SELECT pg_create_physical_replication_slot('sb3_slot');});
+
+$backup_name = 'backup2';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+my $connstr_1 = $primary->connstr;
+$standby3->stop;
+$standby3->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb3_slot'
+});
+$standby3->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+$standby3->start;
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb3_slot'
+));
+$primary->start;
+
+# Restart the standby
+$standby3->restart;
+
+# Wait for the standby to start sync
+my $offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots;}
+  ),
+  'lsub1_slot',
+  'failover slot was created');
+
+# Verify slot properties on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|r",
+  'logical slot has sync_state as ready and failover as true on standby');
+
+# Verify slot properties on the primary
+is( $primary->safe_psql('postgres',
+    q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|n",
+  'logical slot has sync_state as none and failover as true on primary');
+
+# Test to confirm that restart_lsn of the logical slot on the primary is synced to the standby
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_int;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# let the slots get synced on the standby
+sleep 2;
+
+# Get the restart_lsn for the logical slot lsub1_slot on the primary
+my $primary_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that restart_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'restart_lsn of slot lsub1_slot synced to standby');
+
+# Get the confirmed_flush_lsn for the logical slot lsub1_slot on the primary
+$primary_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that confirmed_flush_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub1_slot synced to the standby');
+
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 175bcecfd1..9a79feaddf 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2317,6 +2317,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2575,6 +2576,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorkerCtx
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.34.1



  [application/octet-stream] v43-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (139.6K, ../../CAJpy0uBYJh+6vsX2VnecZvc=TUFMY93O9Pob07VzsQQV3cGBWQ@mail.gmail.com/4-v43-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From 9c376044fd573c7b5763ff3e231bd5e0df43018e Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Wed, 6 Dec 2023 15:52:16 +0530
Subject: [PATCH v43 1/3] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to
the physical standbys so that logical replication can be resumed after
failover. It is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API.

Ex1:
CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

Ex2: (failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

A new replication command called ALTER_REPLICATION_SLOT and a
corresponding walreceiver API function named walrcv_alter_slot have been
implemented. They allow subscribers or users to modify the failover
property of a replication slot on the publisher.

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).
---
 contrib/test_decoding/expected/slot.out       |  58 +++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |  20 +-
 doc/src/sgml/ref/create_subscription.sgml     |  23 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       | 108 +++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  38 +-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  53 ++-
 src/backend/replication/logical/worker.c      |  71 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 182 +++++++++-
 src/backend/replication/slotfuncs.c           |  19 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 336 ++++++++++++++++--
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_dump/pg_dump.c                     |  20 +-
 src/bin/pg_dump/pg_dump.h                     |   1 +
 src/bin/pg_upgrade/info.c                     |   5 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 src/test/recovery/t/050_verify_slot_order.pl  | 149 ++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 +++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 50 files changed, 1379 insertions(+), 170 deletions(-)
 create mode 100644 src/test/recovery/t/050_verify_slot_order.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 94d1eb2b81..30d9b53e03 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4360,6 +4360,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..481e397bad 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
@@ -230,6 +233,17 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
+
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property of the new slot may differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription. When creating the slot,
+      ensure the slot failover property matches the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter value of the subscription.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..fa6cd1c43f 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,29 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..e74fff2305 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,34 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If the slot_name is specified without the create_slot option, it
+			 * is possible that the user intends to use an existing slot on the
+			 * publisher, so here we alter the failover property of the slot to
+			 * match the failover value in subscription.
+			 *
+			 * We do not need to change the failover to false if the server
+			 * does not support failover (e.g. pre-PG17)
+			 */
+			else if (opts.slot_name &&
+					(failover_enabled || walrcv_server_version(wrconn) >= 170000))
+			{
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+								opts.slot_name,
+								failover_enabled ? "true" : "false")));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,13 +1331,21 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
@@ -1334,13 +1394,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why copy_data
+					 * is not allowed when twophase or failover is enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 						/* translator: %s is an SQL ALTER command */
 								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
 										 isadd ?
@@ -1389,7 +1461,19 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 					ereport(ERROR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "two_phase"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "failover"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 4152d1ddbc..4fadd3df7a 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -888,8 +892,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -918,7 +922,8 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+			appendStringInfoString(&cmd, "FAILOVER, ");
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -987,6 +992,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					 bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index df3c42eb5d..d2d8bf1a7a 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -614,15 +614,28 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "two_phase")));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "failover")));
+
 				should_exit = true;
 			}
 		}
@@ -1420,7 +1433,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1722,10 +1736,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1733,9 +1749,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1749,9 +1764,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..cf5d9caa8b 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,6 +132,37 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * failover = true when creating the subscription. Enabling failover allows us
+ * to smoothly transition to the promoted standby, ensuring that we can
+ * subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby. During syncing, the local restart_lsn and/or local catalog_xmin of
+ * the newly created slot on the standby are typically ahead of those on the
+ * primary. Therefore, the standby needs to wait for the primary server's
+ * restart_lsn and catalog_xmin to catch up, which takes time.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
@@ -3947,6 +3978,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4514,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4572,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4612,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..d35e6dec55 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List	   *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -251,7 +263,8 @@ ReplicationSlotValidateName(const char *name, int elevel)
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +324,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +693,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2198,140 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+
+	/* Need a modifiable copy of string */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into list of identifiers */
+	if (!SplitIdentifierString(rawname, ',', &elemlist))
+	{
+		/* syntax error in name list */
+		GUC_check_errdetail("List syntax is invalid.");
+		goto ret_standby_slot_names_ng;
+	}
+
+	/*
+	 * Verify 'type' of slot now.
+	 *
+	 * Skip check if replication slots' data is not initialized yet i.e. we
+	 * are in startup process.
+	 */
+	if (!ReplicationSlotCtl)
+		goto ret_standby_slot_names_ok;
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+			goto ret_standby_slot_names_ng;
+
+		if (!SlotIsPhysical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			goto ret_standby_slot_names_ng;
+		}
+	}
+
+ret_standby_slot_names_ok:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return true;
+
+ret_standby_slot_names_ng:
+
+	pfree(rawname);
+	list_free(elemlist);
+	return false;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..fb6e37d2c3 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -679,6 +689,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +745,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +785,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 26ded928a7..ca61a99785 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -387,7 +387,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..58e298af89 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool	failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,238 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up the logical walsender processes with failover-enabled slots if the
+ * physical slot of the current walsender is specified in standby_slot_names
+ * GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn. Additionally,
+ * it removes slots that have been invalidated, dropped, or converted to
+ * logical slots.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
+		bool		invalidated = false;
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (slot && SlotIsPhysical(slot))
+		{
+			SpinLockAcquire(&slot->mutex);
+			restart_lsn = slot->data.restart_lsn;
+			invalidated = slot->data.invalidated != RS_INVAL_NONE;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Continue if the current slot hasn't caught up. */
+		if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
+			restart_lsn < wait_for_lsn)
+		{
+			/* Log warning if no active_pid for this physical slot */
+			if (slot->active_pid == 0)
+				ereport(WARNING,
+						errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+							   name, "standby_slot_names"),
+						errdetail("Logical replication is waiting on the "
+								  "standby associated with \"%s\"", name),
+						errhint("Consider starting standby associated with "
+								"\"%s\" or amend standby_slot_names", name));
+
+			continue;
+		}
+
+		/*
+		 * It may happen that the slot specified in standby_slot_names GUC
+		 * value is dropped, so let's skip over it.
+		 */
+		else if (!slot)
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+
+		/*
+		 * If a logical slot name is provided in standby_slot_names, issue a
+		 * WARNING and skip it. Although logical slots are disallowed in the
+		 * GUC check_hook(validate_standby_slots), it is still possible for a
+		 * user to drop an existing physical slot and recreate a logical slot
+		 * with the same name. Since it is harmless, a WARNING should be
+		 * enough, no need to error-out.
+		 */
+		else if (SlotIsLogical(slot))
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+
+		/*
+		 * Specified physical slot may have been invalidated, so no point in
+		 * waiting for it.
+		 */
+		else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
+			warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+		else
+			Assert(restart_lsn >= wait_for_lsn);
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving the given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, the
+ * function also waits for all the specified streaming replication standby
+ * servers to confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr if we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
-	if (RecentFlushPtr != InvalidXLogRecPtr &&
-		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	if (!XLogRecPtrIsInvalid(RecentFlushPtr) && loc <= RecentFlushPtr)
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know we
+		 * have enough WAL available and all the standby servers have confirmed
+		 * receipt of WAL up to RecentFlushPtr. This is particularly interesting
+		 * if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1830,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1845,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,8 +1884,14 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
 			break;
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
@@ -1654,9 +1932,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2099,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2336,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3599,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3670,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another CV
+	 * that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 6474e35ec0..4b776266a4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4572,6 +4572,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..5d940b72cd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -329,6 +329,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8c0b5486b9..ea7351cfbc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4618,6 +4618,7 @@ getSubscriptions(Archive *fout)
 	int			i_subsynccommit;
 	int			i_subpublications;
 	int			i_suborigin;
+	int			i_subfailoverstate;
 	int			i,
 				ntups;
 
@@ -4673,14 +4674,22 @@ getSubscriptions(Archive *fout)
 		appendPQExpBufferStr(query,
 							 " s.subpasswordrequired,\n"
 							 " s.subrunasowner,\n"
-							 " s.suborigin\n");
+							 " s.suborigin,\n");
 	else
 		appendPQExpBuffer(query,
 						  " 't' AS subpasswordrequired,\n"
 						  " 't' AS subrunasowner,\n"
-						  " '%s' AS suborigin\n",
+						  " '%s' AS suborigin,\n",
 						  LOGICALREP_ORIGIN_ANY);
 
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(query,
+							 " subfailoverstate\n");
+	else
+		appendPQExpBuffer(query,
+						  " '%c' AS subfailoverstate\n",
+						  LOGICALREP_FAILOVER_STATE_DISABLED);
+
 	appendPQExpBufferStr(query,
 						 "FROM pg_subscription s\n"
 						 "WHERE s.subdbid = (SELECT oid FROM pg_database\n"
@@ -4709,6 +4718,7 @@ getSubscriptions(Archive *fout)
 	i_subsynccommit = PQfnumber(res, "subsynccommit");
 	i_subpublications = PQfnumber(res, "subpublications");
 	i_suborigin = PQfnumber(res, "suborigin");
+	i_subfailoverstate = PQfnumber(res, "subfailoverstate");
 
 	subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo));
 
@@ -4746,6 +4756,8 @@ getSubscriptions(Archive *fout)
 		subinfo[i].subpublications =
 			pg_strdup(PQgetvalue(res, i, i_subpublications));
 		subinfo[i].suborigin = pg_strdup(PQgetvalue(res, i, i_suborigin));
+		subinfo[i].subfailoverstate =
+			pg_strdup(PQgetvalue(res, i, i_subfailoverstate));
 
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(subinfo[i].dobj), fout);
@@ -4771,6 +4783,7 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	int			npubnames = 0;
 	int			i;
 	char		two_phase_disabled[] = {LOGICALREP_TWOPHASE_STATE_DISABLED, '\0'};
+	char		failover_disabled[] = {LOGICALREP_FAILOVER_STATE_DISABLED, '\0'};
 
 	/* Do nothing in data-only dump */
 	if (dopt->dataOnly)
@@ -4818,6 +4831,9 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	if (strcmp(subinfo->subtwophasestate, two_phase_disabled) != 0)
 		appendPQExpBufferStr(query, ", two_phase = on");
 
+	if (strcmp(subinfo->subfailoverstate, failover_disabled) != 0)
+		appendPQExpBufferStr(query, ", failover = true");
+
 	if (strcmp(subinfo->subdisableonerr, "t") == 0)
 		appendPQExpBufferStr(query, ", disable_on_error = true");
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2fe3cbed9a..f62a4dfd4b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -671,6 +671,7 @@ typedef struct _SubscriptionInfo
 	char	   *subsynccommit;
 	char	   *subpublications;
 	char	   *suborigin;
+	char	   *subfailoverstate;
 } SubscriptionInfo;
 
 /*
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..e16286f18c 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,6 +679,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
@@ -687,6 +688,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +699,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..f6ac78418e 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced
+								 * to the physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 020e7aa1cc..cb3a2b3aed 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -159,7 +159,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -179,8 +179,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 049801186c..905964a2e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3327,7 +3327,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb58dee3bc..d906734750 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..ca06e5b1ad 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)?
+	 * Only relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 949e874f21..d5bca2500e 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -355,9 +355,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									   const char *slotname,
+									   bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -399,6 +410,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -428,8 +440,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 60313980a9..ecd212c7b6 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -12,6 +12,8 @@
 #ifndef _WALSENDER_H
 #define _WALSENDER_H
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -45,6 +47,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index db73408937..84bb79ac0f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -256,7 +256,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..3be3ee52fc 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_verify_slot_order.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_verify_slot_order.pl b/src/test/recovery/t/050_verify_slot_order.pl
new file mode 100644
index 0000000000..bff0f52a46
--- /dev/null
+++ b/src/test/recovery/t/050_verify_slot_order.pl
@@ -0,0 +1,149 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres', "CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+$subscriber1->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true
+$subscriber1->safe_psql('postgres',
+		"CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);");
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+$subscriber2->safe_psql('postgres',
+		"CREATE SUBSCRIPTION mysub2 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);");
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby comes
+# up.
+$standby1->stop;
+
+# Create some data on primary
+my $primary_row_count = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = 0 FROM tab_int;");
+is($result, 't', "subscriber1 doesn't get data from primary until standby1 acknowledges changes");
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber1 gets data from primary after standby1 acknowledges changes");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 38a86575e1..175bcecfd1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3865,6 +3866,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-07 07:53                                           ` Drouvot, Bertrand <[email protected]>
  5 siblings, 0 replies; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-12-07 07:53 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 12/6/23 12:23 PM, shveta malik wrote:
> On Wed, Dec 6, 2023 at 4:28 PM shveta malik <[email protected]> wrote:
> 
> 
> PFA v43, changes are:

Thanks!

> 
> v43-001:
> 1) Support of  'failover' dump in pg_dump. It was missing earlier.
> 
> v43-002:
> 1) Slot-sync worker now  checks validity of primary_slot_name by
> connecting to primary, once during its start and later if
> primary_slot_name GUC is changed.

I gave a second thought on it and yeah that sounds like the best option
(as compare to relying on the walreceiver being up or down).

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-07 08:03                                           ` Peter Smith <[email protected]>
  2023-12-11 09:30                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  5 siblings, 1 reply; 113+ messages in thread

From: Peter Smith @ 2023-12-07 08:03 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Amit Kapila <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi.

Here are my review comments for patch v43-0002.

======
Commit message

1.
The nap time of worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. And if
activity is observed again, nap time is reduced back to 10ms.

~
/nap time of worker/nap time of the worker/
/And if/If/

~~~

2.
Slots synced on the standby can be identified using 'sync_state' column of
pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but waiting for the remote slot on the
     primary server to catch up.
'r': ready for periodic syncs.

~

/identified using/identified using the/

The meaning of "identified by" is unclear to me. It also seems to
clash with later descriptions in system-views.sgml. Please see my
later review comment about it (in the sgml file)

======
doc/src/sgml/bgworker.sgml

3.
bgw_start_time is the server state during which postgres should start
the process; it can be one of BgWorkerStart_PostmasterStart (start as
soon as postgres itself has finished its own initialization; processes
requesting this are not eligible for database connections),
BgWorkerStart_ConsistentState (start as soon as a consistent state has
been reached in a hot standby, allowing processes to connect to
databases and run read-only queries), and
BgWorkerStart_RecoveryFinished (start as soon as the system has
entered normal read-write state. Note that the
BgWorkerStart_ConsistentState and BgWorkerStart_RecoveryFinished are
equivalent in a server that's not a hot standby), and
BgWorkerStart_ConsistentState_HotStandby (same meaning as
BgWorkerStart_ConsistentState but it is more strict in terms of the
server i.e. start the worker only if it is hot-standby; if it is
consistent state in non-standby, worker will not be started). Note
that this setting only indicates when the processes are to be started;
they do not stop when a different state is reached.

~

3a.
This seems to have grown to become just one enormous sentence that is
too hard to read. IMO this should be changed to be a <variablelist> of
possible values instead of a big slab of text. I suspect it could also
be simplified quite a lot -- something like below

SUGGESTION
bgw_start_time is the server state during which postgres should start
the process. Note that this setting only indicates when the processes
are to be started; they do not stop when a different state is reached.
Possible values are:

- BgWorkerStart_PostmasterStart (start as soon as postgres itself has
finished its own initialization; processes requesting this are not
eligible for database connections)

- BgWorkerStart_ConsistentState (start as soon as a consistent state
has been reached in a hot-standby, allowing processes to connect to
databases and run read-only queries)

- BgWorkerStart_RecoveryFinished (start as soon as the system has
entered normal read-write state. Note that the
BgWorkerStart_ConsistentState and BgWorkerStart_RecoveryFinished are
equivalent in a server that's not a hot standby)

- BgWorkerStart_ConsistentState_HotStandby (same meaning as
BgWorkerStart_ConsistentState but it is more strict in terms of the
server i.e. start the worker only if it is hot-standby; if it is a
consistent state in non-standby, the worker will not be started).

~~~

3b.
"i.e. start the worker only if it is hot-standby; if it is consistent
state in non-standby, worker will not be started"

~

Why is it even necessary to say the 2nd part "if it is consistent
state in non-standby, worker will not be started". It seems redundant
given 1st part says the same, right?


======
doc/src/sgml/config.sgml

4.
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must enable
+        <varname>enable_syncslot</varname> for the standbys to receive
+        failover logical slots changes from the primary.
+       </para>

4a.
Somehow "must enable enable_syncslot" seemed strange. Maybe re-word like:

"must enable slot synchronization (see enable_syncslot)"

OR

"must configure enable_syncslot = true"

~~~

4b.
(seems like repetitive use of "the standbys")

/for the standbys to/to/

OR

/for the standbys to/so they can/

~~~

5.
           <varname>primary_conninfo</varname> string, or in a separate
-          <filename>~/.pgpass</filename> file on the standby server (use
+          <filename>~/.pgpass</filename> file on the standby server. (use

This rearranged period seems unrelated to the current patch. Maybe
don't touch this.

~~~

6.
+         <para>
+          Specify <literal>dbname</literal> in
+          <varname>primary_conninfo</varname> string to allow synchronization
+          of slots from the primary server to the standby server.
+          This will only be used for slot synchronization. It is ignored
+          for streaming.
          </para>

The wording "to allow synchronization of slots" seemed misleading to
me. Isn't that more the purpose of the 'enable_syncslot' GUC? I think
the intended wording is more like below:

SUGGESTION
If slot synchronization is enabled then it is also necessary to
specify <literal>dbname</literal> in the
<varname>primary_conninfo</varname> string. This will only be used for
slot synchronization. It is ignored for streaming.

======
doc/src/sgml/logicaldecoding.sgml

7.
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and set
+     <varname>enable_syncslot</varname> on the standby. For the synchronization
+     to work, it is mandatory to have physical replication slot between the
+     primary and the standby. This physical replication slot for the standby
+     should be listed in <varname>standby_slot_names</varname> on the primary
+     to prevent the subscriber from consuming changes faster than the hot
+     standby. Additionally, similar to creating a logical replication slot
+     on the hot standby, <varname>hot_standby_feedback</varname> should be
+     set on the standby and a physical slot between the primary and the standby
+     should be used.
+    </para>


7a.
/creation and set/creation and setting/
/to have physical replication/to have a physical replication/

~

7b.
It's unclear why this is saying "should be listed in
standby_slot_names" and "hot_standby_feedback should be set on the
standby". Why is it saying "should" instead of MUST -- are these
optional? I thought the GUC validation function mandates these (???).

~

7c.
Why does the paragraph say "and a physical slot between the primary
and the standby should be used.";  isn't that exactly what was already
written earlier ("For the synchronization to work, it is mandatory to
have physical replication slot between the primary and the standby"

~~~

8.
+    <para>
+     By enabling synchronization of slots, logical replication can be resumed
+     after failover depending upon the
+     <link linkend="view-pg-replication-slots">pg_replication_slots</link>.<structfield>sync_state</structfield>
+     for the synchronized slots on the standby at the time of failover.
+     The slots which were in ready sync_state ('r') on the standby before
+     failover can be used for logical replication after failover. However,
+     the slots which were in initiated sync_state ('i) and were not
+     sync-ready ('r') at the time of failover will be dropped and logical
+     replication for such slots can not be resumed after failover. This applies
+     to the case where a logical subscription is disabled before
failover and is
+     enabled after failover. If the synchronized slot due to disabled
+     subscription could not be made sync-ready ('r') on standby, then the
+     subscription can not be resumed after failover even when enabled.


8a.
This feels overcomplicated -- too much information?

SUGGESTION
depending upon the ... sync_state for the synchronized slots on the
standby at the time of failover. Only slots that were in ready
sync_state ('r') on the standby before failover can be used for
logical replication after failover

~~~

8b.
+     the slots which were in initiated sync_state ('i) and were not
+     sync-ready ('r') at the time of failover will be dropped and logical
+     replication for such slots can not be resumed after failover. This applies
+     to the case where a logical subscription is disabled before
failover and is
+     enabled after failover. If the synchronized slot due to disabled
+     subscription could not be made sync-ready ('r') on standby, then the
+     subscription can not be resumed after failover even when enabled.

But isn't ALL that part pretty much redundant information for the
user? I thought these are not ready state, so they are not usable...
End-Of-Story. Isn't everything else just more like implementation
details, which the user does not need to know about?

~~~

9.
+     If the primary is idle, making the synchronized slot on the standby
+     as sync-ready ('r') for enabled subscription may take noticeable time.
+     This can be sped up by calling the
+     <function>pg_log_standby_snapshot</function> function on the primary.
+    </para>

SUGGESTION
If the primary is idle, then the synchronized slots on the standby may
take a noticeable time to reach the ready ('r') sync_state. This can
be sped up by calling the
<function>pg_log_standby_snapshot</function> function on the primary.

======
doc/src/sgml/system-views.sgml

10.
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has enabled slots synchronization.
+      </para>

I felt that this part "which has enabled slots synchronization" should
cross-reference to the 'sync_enabled' GUC.

~~~

11.
+      <para>
+       State code:
+       <literal>n</literal> = none for user created slots,
+       <literal>i</literal> = sync initiated for the slot but slot is not ready
+        yet for periodic syncs,
+       <literal>r</literal> = ready for periodic syncs.
+      </para>

I'm wondering why don't we just reuse 'd' (disabled), 'p' (pending),
'e' (enabled) like the other tri-state attributes are using.

~~~

12.
+      <para>
+      The hot standby can have any of these sync_state for the slots but on a
+      hot standby, the slots with state 'r' and 'i' can neither be
used for logical
+      decoded nor dropped by the user. The primary server will have sync_state
+      as 'n' for all the slots. But if the standby is promoted to become the
+      new primary server, sync_state can be seen 'r' as well. On this new
+      primary server, slots with sync_state as 'r' and 'n' will
behave the same.
+      </para></entry>
+     </row>

12a.
/logical decoded/logical decoding/

~

12b.
"sync_state as 'r' and 'n' will behave the same" sounds kind of hacky.
Is there no alternative?

Anyway, IMO mentioning about primary server states seems overkill,
because you already said "This is meaningful on the physical standby"
which I took as implying that it is *not* meaningful from the POV of
the primary server.

In light of this, I'm wondering if a better name for this attribute
would be: 'standby_sync_state'

======
src/backend/access/transam/xlogrecovery.c

13.
+ /*
+ * Shutdown the slot sync workers to prevent potential conflicts between
+ * user processes and slotsync workers after a promotion. Additionally,
+ * drop any slots that have initiated but not yet completed the sync
+ * process.
+ */
+ ShutDownSlotSync();
+ slotsync_drop_initiated_slots();
+

Is this where maybe the 'sync_state' should also be updated for
everything so you are not left with confusion about different states
on a node that is no longer a standby node?

======
src/backend/postmaster/postmaster.c

14. PostmasterMain

  ApplyLauncherRegister();

+ SlotSyncWorkerRegister();
+

Every other function call here is heavily commented but there is a
conspicuous absence of a comment here.

~~~

15. bgworker_should_start_now

  if (start_time == BgWorkerStart_ConsistentState)
  return true;
+ else if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
+ pmState != PM_RUN)
+ return true;
  /* fall through */
Change "else if" to "if" would be simpler.

======
.../libpqwalreceiver/libpqwalreceiver.c

16.
+ for (opt = opts; opt->keyword != NULL; ++opt)
+ {
+ /*
+ * If multiple dbnames are specified, then the last one will be
+ * returned
+ */
+ if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+ opt->val[0] != '\0')
+ dbname = pstrdup(opt->val);
+ }

This can use a tidier C99 style to declare 'opt' as the loop variable.

~~~

17.
 static void
 libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
- bool failover)
+ bool failover)

What is this change for? Or, if something is wrong with the indent
then anyway it should be fixed in patch 0001.

======
src/backend/replication/logical/logical.c

18.

+ /*
+ * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+ * promotion.
+ */
+ if (!RecoveryInProgress() && slot->data.sync_state ==
SYNCSLOT_STATE_INITIATED)
+ elog(ERROR, "replication slot \"%s\" was not synced completely from
the primary server",
+ NameStr(slot->data.name));
+
+ /*
+ * Do not allow consumption of a "synchronized" slot until the standby
+ * gets promoted.
+ */
+ if (RecoveryInProgress() && slot->data.sync_state != SYNCSLOT_STATE_NONE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot use replication slot \"%s\" for logical decoding",
+ NameStr(slot->data.name)),
+ errdetail("This slot is being synced from the primary server."),
+ errhint("Specify another replication slot.")));
+

18a.

Instead of having !RecoveryInProgress() and RecoveryInProgress() in
separate conditions is the code simpler like:

SUGGESTION

if (RecoveryInProgress())
{
  /* Do not allow ... */
  if (slot->data.sync_state != SYNCSLOT_STATE_NONE) ...
}
else
{
  /* Slots in state... */
  if (slot->data.sync_state == SYNCSLOT_STATE_INITIATED) ...
}

~

18b.
Should the errdetail give the current state?

======
src/backend/replication/logical/slotsync.c

19.
+/*
+ * Number of attempts for wait_for_primary_slot_catchup() after
+ * which it aborts the wait and the slot sync worker then moves
+ * to the next slot creation/sync.
+ */
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5

Given this is only used within one static function, I'm wondering if
it would be tidier to also move this macro to within that function.

~~~

20. wait_for_primary_slot_catchup

+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * *persist will be set to false if the slot has disappeared or was invalidated
+ * on the primary; otherwise, it will be set to true.
+ */

20a.
The comment doesn't say the meaning of the boolean returned.

~

20b.
/*persist will be set/If passed, *persist will be set/

~~~

21.
+ appendStringInfo(&cmd,
+ "SELECT conflicting, restart_lsn, confirmed_flush_lsn,"
+ " catalog_xmin FROM pg_catalog.pg_replication_slots"
+ " WHERE slot_name = %s",
+ quote_literal_cstr(remote_slot->name));

Somehow, I felt it is more readable if the " FROM" starts on a new line.

e.g.
"SELECT conflicting, restart_lsn, confirmed_flush_lsn, catalog_xmin"
" FROM pg_catalog.pg_replication_slots"
" WHERE slot_name = %s"

~~~

22.
+ ereport(ERROR,
+ (errmsg("could not fetch slot info for slot \"%s\" from the"
+ " primary server: %s",
+ remote_slot->name, res->err)));

Perhaps the message can be shortened like:
"could not fetch slot \"%s\" info from the primary server: %s"

~~~

23.
+ ereport(WARNING,
+ (errmsg("slot \"%s\" disappeared from the primary server,"
+ " slot creation aborted", remote_slot->name)));

Would this be better split into parts?

SUGGESTION
errmsg "slot \"%s\" creation aborted"
errdetail "slot was not found on the primary server"

~~~

24.
+ ereport(WARNING,
+ (errmsg("slot \"%s\" invalidated on the primary server,"
+ " slot creation aborted", remote_slot->name)));

(similar to previous)

SUGGESTION
errmsg "slot \"%s\" creation aborted"
errdetail "slot was invalidated on the primary server"

~~~

25.
+ /*
+ * Once we got valid restart_lsn, then confirmed_lsn and catalog_xmin
+ * are expected to be valid/non-null.
+ */

SUGGESTION
Having got a valid restart_lsn, the confirmed_lsn and catalog_xmin are
expected to be valid/non-null.

~~~

26. slotsync_drop_initiated_slots

+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up.
+ */

I found "waiting for the primary server to catch up" to be difficult
to understand without knowing the full details, but it is not really
described properly until a much larger comment that is buried in the
synchronize_one_slot(). So I think all this needs explanation up-front
in the file, which you can refer to. I have repeated this same review
comment in a couple of places.

~~~

27. get_local_synced_slot_names

+static List *
+get_local_synced_slot_names(void)
+{
+ List    *localSyncedSlots = NIL;

27a.
It's not returning a list of "names" though, so is this an appropriate
function name?

~~~

27b.
Suggest just call that ('localSyncedSlots') differently.
- In slotsync_drop_initiated_slots() function they are just called 'slots'
- In drop_obsolete_slots() function it is called 'local_slot_list'

IMO it is better if all these are consistently named -- just all lists
'slots' or all 'local_slots' or whatever.

~~~

28. check_sync_slot_validity

+static bool
+check_sync_slot_validity(ReplicationSlot *local_slot, List *remote_slots,
+ bool *locally_invalidated)

Somehow this wording "validity" seems like a misleading function name,
because the return value has nothing to do with the slot field
invalidated.

The validity/locally_invalidated stuff is a secondary return as a side
effect for the "true" case.

A more accurate function name would be more like check_sync_slot_on_remote().

~~~

29. check_sync_slot_validity

+static bool
+check_sync_slot_validity(ReplicationSlot *local_slot, List *remote_slots,
+ bool *locally_invalidated)
+{
+ ListCell   *cell;

There is inconsistent naming --

ListCell lc; ListCell cell; ListCell lc_slot; etc..

IMO the more complicated names aren't of much value -- probably
everything can be changed to 'lc' for consistency.

~~~

30. drop_obsolete_slots

+ /*
+ * Get the list of local 'synced' slot so that those not on remote could
+ * be dropped.
+ */

/slot/slots/

Also, I don't think it is necessary to say "so that those not on
remote could be dropped." -- That is already described in the function
comment and again in a comment later in the loop. That seems enough.
If the function name get_local_synced_slot_names() is improved a bit
the comment seems redundant because it is obvious from the function
name.

~~~

31.
+ foreach(lc_slot, local_slot_list)
+ {
+ ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc_slot);
+ bool local_exists = false;
+ bool locally_invalidated = false;
+
+ local_exists = check_sync_slot_validity(local_slot, remote_slot_list,
+ &locally_invalidated);

Shouldn't that 'local_exists' variable be called 'remote_exists'?
That's what the other comments seem to be saying.

~~~

32. construct_slot_query

+ appendStringInfo(s,
+ "SELECT slot_name, plugin, confirmed_flush_lsn,"
+ " restart_lsn, catalog_xmin, two_phase, failover,"
+ " database, pg_get_slot_invalidation_cause(slot_name)"
+ " FROM pg_catalog.pg_replication_slots"
+ " WHERE failover and sync_state != 'i'");

Just wondering if substituting the SYNCSLOT_STATE_INITIATED constant
here might be more appropriate than hardwiring 'i'. Why have a
constant but not use it?

~~~

33. synchronize_one_slot

+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+ bool *slot_updated)
+{
+ ReplicationSlot *s;
+ char sync_state = 0;

33a.
It seems strange that the sync_state is initially assigned something
other than the 3 legal values. Should this be defaulting to
SYNCSLOT_STATE_NONE instead?

~

33b.
I think it is safer to default the *slot_updated = false; because the
code appears to assume it was false already which may or may not be
true.

~~~

34.
+ /*
+ * Make sure that concerned WAL is received before syncing slot to target
+ * lsn received from the primary server.
+ *
+ * This check should never pass as on the primary server, we have waited
+ * for the standby's confirmation before updating the logical slot.
+ */

Maybe this comment should mention up-front that it is just a "Sanity check:"

~~~

35.
+ /*
+ * With hot_standby_feedback enabled and invalidations handled
+ * apropriately as above, this should never happen.
+ */
+ if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn)
+ {
+ ereport(ERROR,
+ errmsg("not synchronizing local slot \"%s\" LSN(%X/%X)"
+    " to remote slot's LSN(%X/%X) as synchronization "
+    " would move it backwards", remote_slot->name,
+    LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+    LSN_FORMAT_ARGS(remote_slot->restart_lsn)));
+
+ goto cleanup;
+ }

35a.
IIUC then this another comment that should say it is just a "Sanity-check:".

~

35b.
I was wondering if there should be Assert(hot_standby_feedback) here
also. The comment "With hot_standby_feedback enabled" is a bit vague
whereas including an Assert will clarify that it must be set.

~

35c.
Since it says "this should never happen" then it appears elog is more
appropriate than ereport because translations are not needed, right?

~

35d.
The ERROR will make that goto cleanup unreachable, won't it?

~~~

36.
+ /*
+ * Already existing slot but not ready (i.e. waiting for the primary
+ * server to catch-up), lets attempt to make it sync-ready now.
+ */

/lets/let's/

~~~

37.
+ /*
+ * Refer the slot creation part (last 'else' block) for more details
+ * on this wait.
+ */
+ if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+ TransactionIdPrecedes(remote_slot->catalog_xmin,
+   MyReplicationSlot->data.catalog_xmin))
+ {
+ if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+ {
+ goto cleanup;
+ }
+ }

37a.
Having to jump forward to understand earlier code seems backward. IMO
there should be a big comment atop this module about this subject
which the comment here can just refer to. I will write more about this
topic later (below).

~

37b.
The extra code curly braces are not needed.

~~~

38.
+ ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready "
+ "now", remote_slot->name));

Better to put the whole errmsg() on a newline instead of splitting the
string like that.

~~~

39.
+ /* User created slot with the same name exists, raise ERROR. */
+ else if (sync_state == SYNCSLOT_STATE_NONE)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("skipping sync of slot \"%s\" as it is a user created"
+ " slot", remote_slot->name),
+ errdetail("This slot has failover enabled on the primary and"
+    " thus is sync candidate but user created slot with"
+    " the same name already exists on the standby")));
+ }

I felt it would be better to eliminate this case immediately up-front
when you first searched for the slot names. e.g. code like below. IIUC
this refactor also means the default sync_state can be assigned a
normal value (as I suggested above) instead of the strange assignment
to 0.

+ /* Search for the named slot */
+ if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+ {
+ SpinLockAcquire(&s->mutex);
+ sync_state = s->data.sync_state;
+ SpinLockRelease(&s->mutex);

INSERT HERE
+     /* User-created slot with the same name exists, raise ERROR. */
+     if (sync_state == SYNCSLOT_STATE_NONE)
+     ereport(ERROR, ...
+ }

~~~

40.
+ /* Otherwise create the slot first. */
+ else
+ {

Insert a blank line above that comment for better readability (same as
done for earlier 'else' in this same function)

~~~

41.
+ ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+   remote_slot->two_phase,
+   remote_slot->failover,
+   SYNCSLOT_STATE_INITIATED);
+
+ slot = MyReplicationSlot;

In hindsight, the prior if/else code blocks in this function also
could have done "slot = MyReplicationSlot;" same as this -- then the
code would be much less verbose.

~~~

42.
+ SpinLockAcquire(&slot->mutex);
+ slot->data.database = get_database_oid(remote_slot->database, false);
+
+ namestrcpy(&slot->data.plugin, remote_slot->plugin);
+ SpinLockRelease(&slot->mutex);

IMO the code would be more readable *without* a blank line here
because the mutexed block is more obvious.

~~~

43.
+ /*
+ * If the local restart_lsn and/or local catalog_xmin is ahead of
+ * those on the remote then we cannot create the local slot in sync
+ * with the primary server because that would mean moving the local
+ * slot backwards and we might not have WALs retained for old LSN. In
+ * this case we will wait for the primary server's restart_lsn and
+ * catalog_xmin to catch up with the local one before attempting the
+ * sync.
+ */

43a.
This comment describes some fundamental concepts about how this logic
works. I felt this and other comments like this should be at the top
of this slotsync.c file. Then anything that needs to mention about it
can refer to the top comment. For example, I also found other comments
like "... they are still waiting for the primary server to catch up."
to be difficult to understand without knowing these details, but I
think describing core design stuff up-front and saying "refer to the
comment atop the fil" probably would help a lot.

~

43b.
Should "wait for the primary server's restart_lsn and..." be "wait for
the primary server slot's restart_lsn and..." ?

~~~

44.
+ {
+ bool persist;
+
+ if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &persist))
+ {
+ /*
+ * The remote slot didn't catch up to locally reserved
+ * position.
+ *
+ * We do not drop the slot because the restart_lsn can be
+ * ahead of the current location when recreating the slot in
+ * the next cycle. It may take more time to create such a
+ * slot. Therefore, we persist it (provided remote-slot is
+ * still valid) and attempt the wait and synchronization in
+ * the next cycle.
+ */
+ if (persist)
+ {
+ ReplicationSlotPersist();
+ *slot_updated = true;
+ }
+
+ goto cleanup;
+ }
+ }

Looking at the way this 'persist' parameter is used I felt is it too
complicated. IIUC the wait_for_primary_slot_catchup can only return
*persist = true (for a false return) when it has reached/exceeded the
number of retries and still not yet caught up. Why should
wait_for_primary_slot_catchup() pretend to know about persistence?

In other words, I thought a more meaningful parameter/variable name
(instead of 'persist') is something like 'wait_attempts_exceeded'. IMO
that will make wait_for_primary_slot_catchup() code easier, and here
you can just say like below, where the code matches the comment
better. Thoughts?

+ if (wait_attempts_exceeded)
+ {
+ ReplicationSlotPersist();
+ *slot_updated = true;
+ }

~~~

45.
+
+
+ /*
+ * Wait for primary is either not needed or is over. Update the lsns
+ * and mark the slot as READY for further syncs.
+ */

Double blank lines?

~~~

46.
+ ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready "
+ "now", remote_slot->name));
+ }
+
+cleanup:

Better to put the whole errmsg() on a newline instead of splitting the
string like that.

~~~

47. synchronize_slots

+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and update
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns nap time for the next sync-cycle.
+ */
+static long
+synchronize_slots(WalReceiverConn *wrconn)

/update/updates/

~~~

48.
+ /* The primary_slot_name is not set yet or WALs not received yet */
+ SpinLockAcquire(&WalRcv->mutex);
+ if (!WalRcv ||
+ (WalRcv->slotname[0] == '\0') ||
+ XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+ {
+ SpinLockRelease(&WalRcv->mutex);
+ return naptime;
+ }
+ SpinLockRelease(&WalRcv->mutex);

Just wondering if the scenario of "WALS not received" is a bit more
like "no activity" so perhaps the naptime returned should be
WORKER_INACTIVITY_NAPTIME_MS here?


~~~

49.
+ /* Construct query to get slots info from the primary server */
+ initStringInfo(&s);
+ construct_slot_query(&s);

I did not like the construct_slot_query() to be separated from this
function because it makes it too difficult to see if the slot_attr
numbers and column types in this function are correct w.r.t. that
query. IMO better when everything is in the same place where you can
see it all together. e.g. Less risk of breaking something if changes
are made.

~~~

50.
+ /* Construct the remote_slot tuple and synchronize each slot locally */
+ slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);

Normally in all the other functions the variable 'slot' was the local
ReplicationSlot but IIUC here represents a remote tuple. Making a
different name would be better like 'remote_slottup' or something
else.

~~~

51.
+ /*
+ * If any of the slots get updated in this sync-cycle, retain default
+ * naptime and update 'last_update_time' in slot sync worker. But if no
+ * activity is observed in this sync-cycle, then increase naptime provided
+ * inactivity time reaches threshold.
+ */

I think "retain" is a slightly wrong word here because it might have
been WORKER_INACTIVITY_NAPTIME_MS in the previous cycle.

Maybe just /retain/use/

~~~

52.
+/*
+ * Connects primary to validate the slot specified in primary_slot_name.
+ *
+ * Exits the worker if physical slot with the specified name does not exist.
+ */
+static void
+validate_primary_slot(WalReceiverConn *wrconn)

There is already a connection, so not sure if this connect should be
saying "connects to"; Maybe is should be saying more like below:

SUGGESTION
Using the specified primary server connection, validate if the
physical slot identified by GUC primary_slot_name exists.

Exit the worker if the slot is not found.

~~~

53.
+ initStringInfo(&cmd);
+ appendStringInfo(&cmd,
+ "select count(*) = 1 from pg_replication_slots where "
+ "slot_type='physical' and slot_name=%s",
+ quote_literal_cstr(PrimarySlotName));

Write the SQL keywords in uppercase.

~~~

54.
+ if (res->status != WALRCV_OK_TUPLES)
+ ereport(ERROR,
+ (errmsg("could not fetch primary_slot_name info from the "
+ "primary: %s", res->err)));

Shouldn't the name of the unfound slot be shown in the ereport, or
will that already appear in the res->err?

~~~

55.
+ ereport(ERROR,
+ errmsg("exiting slots synchronization as slot specified in "
+    "primary_slot_name is not valid"));
+

IMO the format should be the same as I suggested (later) for all the
validate_slotsync_parameters() errors.

Also, I think the name of the unfound slot needs to be in this message.

So maybe result is like this:

SUGGESTION

ereport(ERROR,
  errmsg("exiting from slot synchronization due to bad configuration")
  /* translator: second %s is a GUC variable name */
  errhint("The primary slot \"%s\" specified by %s is not valid.",
slot_name, "primary_slot_name")
);

~~~

56.
+/*
+ * Checks if GUCs are set appropriately before starting slot sync worker
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+ /*
+ * Since 'enable_syncslot' is ON, check that other GUC settings
+ * (primary_slot_name, hot_standby_feedback, wal_level, primary_conninfo)
+ * are compatible with slot synchronization. If not, raise ERROR.
+ */
+

56a.
I thought that 2nd comment sort of belonged in the function comment.

~

56b.
It says "Since 'enable_syncslot' is ON", but I IIUC that is wrong
because the other function slotsync_reread_config() might detect a
change in this GUC and cause this validate_slotsync_parameters() to be
called when enable_syncslot was changed to false.

In other words, I think you also need to check 'enable_syncslot' and
exit with appropriate ERROR same as all the other config problems.

OTOH if this is not possible, then the slotsync_reread_config() might
need fixing instead.

~~~

57.
+ /*
+ * A physical replication slot(primary_slot_name) is required on the
+ * primary to ensure that the rows needed by the standby are not removed
+ * after restarting, so that the synchronized slot on the standby will not
+ * be invalidated.
+ */
+ if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+ ereport(ERROR,
+ errmsg("exiting slots synchronization as primary_slot_name is "
+    "not set"));
+
+ /*
+ * Hot_standby_feedback must be enabled to cooperate with the physical
+ * replication slot, which allows informing the primary about the xmin and
+ * catalog_xmin values on the standby.
+ */
+ if (!hot_standby_feedback)
+ ereport(ERROR,
+ errmsg("exiting slots synchronization as hot_standby_feedback "
+    "is off"));
+
+ /*
+ * Logical decoding requires wal_level >= logical and we currently only
+ * synchronize logical slots.
+ */
+ if (wal_level < WAL_LEVEL_LOGICAL)
+ ereport(ERROR,
+ errmsg("exiting slots synchronisation as it requires "
+    "wal_level >= logical"));
+
+ /*
+ * The primary_conninfo is required to make connection to primary for
+ * getting slots information.
+ */
+ if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+ ereport(ERROR,
+ errmsg("exiting slots synchronization as primary_conninfo "
+    "is not set"));
+
+ /*
+ * The slot sync worker needs a database connection for walrcv_exec to
+ * work.
+ */
+ *dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+ if (*dbname == NULL)
+ ereport(ERROR,
+ errmsg("exiting slots synchronization as dbname is not "
+    "specified in primary_conninfo"));
+
+}

IMO all these errors can be improved by:
- using a common format
- including errhint for the reason
- using the same tone for instructions on what to do (e.g saying must
be set, rather than what was not set)

SUGGESTION (something like this)

ereport(ERROR,
  errmsg("exiting from slot synchronization due to bad configuration")
  /* translator: %s is a GUC variable name */
  errhint("%s must be defined.", "primary_slot_name")
);

ereport(ERROR,
  errmsg("exiting from slot synchronization due to bad configuration")
  /* translator: %s is a GUC variable name */
  errhint("%s must be enabled.", "hot_standby_feedback")
);

ereport(ERROR,
  errmsg("exiting from slot synchronization due to bad configuration")
  /* translator: wal_level is a GUC variable name, 'logical' is a value */
  errhint("wal_level must be >= logical.")
);

ereport(ERROR,
  errmsg("exiting from slot synchronization due to bad configuration")
  /* translator: %s is a GUC variable name */
  errhint("%s must be defined.", "primary_conninfo")
);

ereport(ERROR,
  errmsg("exiting from slot synchronization due to bad configuration")
  /* translator: 'dbname' is a specific option; %s is a GUC variable name */
  errhint("'dbname' must be specified in %s.", "primary_conninfo")
);

~~~

58.
+ *dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+ if (*dbname == NULL)
+ ereport(ERROR,
+ errmsg("exiting slots synchronization as dbname is not specified in
primary_conninfo"));
+
+}

Unnecessary blank line at the end of the function

~~~

59.
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs changed, validate the values again
+ * through validate_slotsync_parameters() which will exit the worker
+ * if validaity fails.
+ */

SUGGESTION
If any of the slot sync GUCs have changed, re-validate them. The
worker will exit if the check fails.

~~~

60.
+ char    *conninfo = pstrdup(PrimaryConnInfo);
+ char    *slotname = pstrdup(PrimarySlotName);
+ bool syncslot = enable_syncslot;
+ bool standbyfeedback = hot_standby_feedback;

For clarity, I would have used var names to match the old GUCs.

e.g.
/conninfo/old_primary_conninfo/
/slotname/old_primary_slot_name/
/syncslot/old_enable_syncslot/
/standbyfeedback/old_hot_standby_feedback/

~~~

61.
+ dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+ Assert(dbname);

This code seems premature. IIUC this is only needed to detect that the
dbname was changed. But I think the prerequisite is first that the
conninfoChanged is true. So really this code should be guarded by if
(conninfoChanged) so it can be done later in the function.

~~~

62.
+ if (conninfoChanged || slotnameChanged ||
+ (syncslot != enable_syncslot) ||
+ (standbyfeedback != hot_standby_feedback))
+ {
+ revalidate = true;
+ }

SUGGESTION

revalidate = conninfoChanged || slotnameChanged ||
 (syncslot != enable_syncslot) ||
 (standbyfeedback != hot_standby_feedback);

~~~

63.
+ /*
+ * Since we have initialized this worker with old dbname, thus exit if
+ * dbname changed. Let it get restarted and connect to new dbname
+ * specified.
+ */
+ if (conninfoChanged && strcmp(dbname, new_dbname) != 0)
+ {
+ ereport(ERROR,
+ errmsg("exiting slot sync woker as dbname in "
+    "primary_conninfo changed"));
+ }

63a.
/old dbname/the old dbname/
/new dbname/the new dbname/
/woker/worker/

~

63b.
This code feels awkward. Can't this dbname check and accompanying
ERROR message be moved down into validate_slotsync_parameters(), so it
lives along with all the other GUC validation logic? Maybe you'll need
to change the validate_slotsync_parameters() parameters slightly but I
think it is much better to keep all the validation together.

~~~

64.
+
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)

Double blank lines.

~~~

65.
+
+
+ if (ConfigReloadPending)
+ slotsync_reread_config();
+}

Double blank lines

~~~

66. slotsync_worker_onexit

+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+ SpinLockAcquire(&SlotSyncWorker->mutex);
+ SlotSyncWorker->pid = 0;
+ SpinLockRelease(&SlotSyncWorker->mutex);
+}

Should assignment use InvalidPid (-1) instead of 0?

~~~

67. ReplSlotSyncWorkerMain

+ SpinLockAcquire(&SlotSyncWorker->mutex);
+
+ Assert(SlotSyncWorker->pid == 0);
+
+ /* Advertise our PID so that the startup process can kill us on promotion */
+ SlotSyncWorker->pid = MyProcPid;
+
+ SpinLockRelease(&SlotSyncWorker->mutex);

Shouldn't pid start as InvalidPid (-1) instead of Assert 0?

~~~

68.
+ /* Connect to the primary server */
+ wrconn = remote_connect();
+
+ /*
+ * Connect to primary and validate the slot specified in
+ * primary_slot_name.
+ */
+ validate_primary_slot(wrconn);

Maybe needs some slight rewording in the 2nd comment. "Connect to
primary server" is already said and done in the 1st part.

~~~

69. IsSlotSyncWorker

+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsSlotSyncWorker(void)
+{
+ return SlotSyncWorker->pid == MyProcPid;
+}

69a.
For consistency with others like it, I thought this be called
IsLogicalSlotSyncWorker().

~

69b.
For consistency with the others like this, I think the extern should
be declared in logicalworker.h

~~~

70. ShutDownSlotSync

+ SpinLockAcquire(&SlotSyncWorker->mutex);
+ if (!SlotSyncWorker->pid)
+ {
+ SpinLockRelease(&SlotSyncWorker->mutex);
+ return;
+ }

IMO should be comparing with InvalidPid (-1) here; not 0.

~~~

71.
+ SpinLockAcquire(&SlotSyncWorker->mutex);
+
+ /* Is it gone? */
+ if (!SlotSyncWorker->pid)
+ break;
+
+ SpinLockRelease(&SlotSyncWorker->mutex);

Ditto. bad pids should be InvalidPid (-1), not 0.

~~~

72. SlotSyncWorkerShmemInit

+ if (!found)
+ {
+ memset(SlotSyncWorker, 0, size);
+ SpinLockInit(&SlotSyncWorker->mutex);
+ }

Probably here the unassigned pid should be set to InvalidPid (-1), not 0.

~~~

73. SlotSyncWorkerRegister

+ if (!enable_syncslot)
+ {
+ ereport(LOG,
+ errmsg("skipping slots synchronization as enable_syncslot is "
+    "disabled."));
+ return;
+ }

/as/because/

======
src/backend/replication/logical/tablesync.c

74.
 #include "commands/copy.h"
+#include "commands/subscriptioncmds.h"
 #include "miscadmin.h"

There were only #include changes but no code changes. Is the #include needed?

======
src/backend/replication/slot.c

75. ReplicationSlotCreate

 void
 ReplicationSlotCreate(const char *name, bool db_specific,
    ReplicationSlotPersistency persistency,
-   bool two_phase, bool failover)
+   bool two_phase, bool failover, char sync_state)

The function comment goes to trouble to describe all the parameters
except for 'failover' and 'sync_slate'. I think a failover comment
should be added in patch 0001 and then the sync_state comment should
be added in patch 0002.

~~~

76.
+ /*
+ * Do not allow users to drop the slots which are currently being synced
+ * from the primary to the standby.
+ */
+ if (user_cmd && RecoveryInProgress() &&
+ MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+ {
+ ReplicationSlotRelease();
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot drop replication slot \"%s\"", name),
+ errdetail("This slot is being synced from the primary.")));
+ }

Should the errdetail give the current state?


======
src/backend/tcop/postgres.c

77.
+ else if (IsSlotSyncWorker())
+ {
+ ereport(DEBUG1,
+ (errmsg_internal("replication slot sync worker is shutting down due
to administrator command")));
+
+ /*
+ * Slot sync worker can be stopped at any time.
+ * Use exit status 1 so the background worker is restarted.
+ */
+ proc_exit(1);
+ }

Explicitly saying "ereport(DEBUG1, errmsg_internal(..." is a bit
overkill; it is simpler to write this as "elog(DEBUG1, ....);

======
src/include/replication/slot.h

78.
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n' /* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i' /* Sync initiated for the slot but
+ * not completed yet, waiting for
+ * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r' /* Initialization complete, ready
+ * to be synced further */

Already questioned the same elsewhere. IIUC the same tri-state values
of other attributes might be used here too without needing to
introduce 3 new values.

e.g.

#define SYNCSLOT_STATE_DISABLED 'd' /* No syncing for this slot */
#define SYNCSLOT_STATE_PENDING  'p' /* Sync is enabled but we must
wait for the primary server to catch up */
#define SYNCSLOT_STATE_ENABLED  'e' /* Sync is enabled and the slot is
ready to be synced */

~~~

79.
+ /*
+ * Is this a slot created by a sync-slot worker?
+ *
+ * Relevant for logical slots on the physical standby.
+ */
+ char sync_state;
+

I assumed that "Relevant for" means "Only relevant for". It should say that.

If correct, IMO a better field name might be 'standby_sync_state'

======
src/test/recovery/t/050_verify_slot_order.pl

80.
+$backup_name = 'backup2';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+ $primary, $backup_name,
+ has_streaming => 1,
+ has_restoring => 1);

The mixture of 'backup2' for 'standby3' seems confusing. Is there a
reason to call it backup2?

~~~

81.
+# Verify slot properties on the standby
+is( $standby3->safe_psql('postgres',
+ q{SELECT failover, sync_state FROM pg_replication_slots WHERE
slot_name = 'lsub1_slot';}
+  ),
+  "t|r",
+  'logical slot has sync_state as ready and failover as true on standby');

It might be better if the message has the same order as the SQL. Eg.
"failover as true and sync_state as ready".

~~~

82.
+# Verify slot properties on the primary
+is( $primary->safe_psql('postgres',
+    q{SELECT failover, sync_state FROM pg_replication_slots WHERE
slot_name = 'lsub1_slot';}
+  ),
+  "t|n",
+  'logical slot has sync_state as none and failover as true on primary');
+

It might be better if the message has the same order as the SQL. Eg.
"failover as true and sync_state as none".

~~~

83.
+# Test to confirm that restart_lsn of the logical slot on the primary
is synced to the standby

IMO the major test parts (like this one) may need more highlighting "#
---------------------" so those comments don't get lost among all the
other comments.

~~~

84.
+# let the slots get synced on the standby
+sleep 2;

Won't this make the test prone to failure on slow machines? Is there
not a more deterministic way to wait for the sync?

======
Kind Regards,
Peter Smith.
Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 08:03                                           ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
@ 2023-12-11 09:30                                             ` shveta malik <[email protected]>
  2023-12-18 12:11                                               ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-11 09:30 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Amit Kapila <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Thu, Dec 7, 2023 at 1:33 PM Peter Smith <[email protected]> wrote:
>
> Hi.
>
> Here are my review comments for patch v43-0002.
>

Thanks for the feedback. I have addressed most of these in v45. Please
find my response on a few which are pending or are not needed.

> ======
> Commit message
>
> 1.
> The nap time of worker is tuned according to the activity on the primary.
> The worker starts with nap time of 10ms and if no activity is observed on
> the primary for some time, then nap time is increased to 10sec. And if
> activity is observed again, nap time is reduced back to 10ms.
>
> ~
> /nap time of worker/nap time of the worker/
> /And if/If/
>
> ~~~
>
> 2.
> Slots synced on the standby can be identified using 'sync_state' column of
> pg_replication_slots view. The values are:
> 'n': none for user slots,
> 'i': sync initiated for the slot but waiting for the remote slot on the
>      primary server to catch up.
> 'r': ready for periodic syncs.
>
> ~
>
> /identified using/identified using the/
>
> The meaning of "identified by" is unclear to me. It also seems to
> clash with later descriptions in system-views.sgml. Please see my
> later review comment about it (in the sgml file)
>

I have rephrased it, please check now and let me know.

> ======
> doc/src/sgml/bgworker.sgml
>
> 3.
> bgw_start_time is the server state during which postgres should start
> the process; it can be one of BgWorkerStart_PostmasterStart (start as
> soon as postgres itself has finished its own initialization; processes
> requesting this are not eligible for database connections),
> BgWorkerStart_ConsistentState (start as soon as a consistent state has
> been reached in a hot standby, allowing processes to connect to
> databases and run read-only queries), and
> BgWorkerStart_RecoveryFinished (start as soon as the system has
> entered normal read-write state. Note that the
> BgWorkerStart_ConsistentState and BgWorkerStart_RecoveryFinished are
> equivalent in a server that's not a hot standby), and
> BgWorkerStart_ConsistentState_HotStandby (same meaning as
> BgWorkerStart_ConsistentState but it is more strict in terms of the
> server i.e. start the worker only if it is hot-standby; if it is
> consistent state in non-standby, worker will not be started). Note
> that this setting only indicates when the processes are to be started;
> they do not stop when a different state is reached.
>
> ~
>
> 3a.
> This seems to have grown to become just one enormous sentence that is
> too hard to read. IMO this should be changed to be a <variablelist> of
> possible values instead of a big slab of text. I suspect it could also
> be simplified quite a lot -- something like below
>
> SUGGESTION
> bgw_start_time is the server state during which postgres should start
> the process. Note that this setting only indicates when the processes
> are to be started; they do not stop when a different state is reached.
> Possible values are:
>
> - BgWorkerStart_PostmasterStart (start as soon as postgres itself has
> finished its own initialization; processes requesting this are not
> eligible for database connections)
>
> - BgWorkerStart_ConsistentState (start as soon as a consistent state
> has been reached in a hot-standby, allowing processes to connect to
> databases and run read-only queries)
>
> - BgWorkerStart_RecoveryFinished (start as soon as the system has
> entered normal read-write state. Note that the
> BgWorkerStart_ConsistentState and BgWorkerStart_RecoveryFinished are
> equivalent in a server that's not a hot standby)
>
> - BgWorkerStart_ConsistentState_HotStandby (same meaning as
> BgWorkerStart_ConsistentState but it is more strict in terms of the
> server i.e. start the worker only if it is hot-standby; if it is a
> consistent state in non-standby, the worker will not be started).
>
> ~~~
>
> 3b.
> "i.e. start the worker only if it is hot-standby; if it is consistent
> state in non-standby, worker will not be started"
>
> ~
>
> Why is it even necessary to say the 2nd part "if it is consistent
> state in non-standby, worker will not be started". It seems redundant
> given 1st part says the same, right?
>
>
> ======
> doc/src/sgml/config.sgml
>
> 4.
> +       <para>
> +        The standbys corresponding to the physical replication slots in
> +        <varname>standby_slot_names</varname> must enable
> +        <varname>enable_syncslot</varname> for the standbys to receive
> +        failover logical slots changes from the primary.
> +       </para>
>
> 4a.
> Somehow "must enable enable_syncslot" seemed strange. Maybe re-word like:
>
> "must enable slot synchronization (see enable_syncslot)"
>
> OR
>
> "must configure enable_syncslot = true"
>
> ~~~
>
> 4b.
> (seems like repetitive use of "the standbys")
>
> /for the standbys to/to/
>
> OR
>
> /for the standbys to/so they can/
>
> ~~~
>
> 5.
>            <varname>primary_conninfo</varname> string, or in a separate
> -          <filename>~/.pgpass</filename> file on the standby server (use
> +          <filename>~/.pgpass</filename> file on the standby server. (use
>
> This rearranged period seems unrelated to the current patch. Maybe
> don't touch this.
>
> ~~~
>
> 6.
> +         <para>
> +          Specify <literal>dbname</literal> in
> +          <varname>primary_conninfo</varname> string to allow synchronization
> +          of slots from the primary server to the standby server.
> +          This will only be used for slot synchronization. It is ignored
> +          for streaming.
>           </para>
>
> The wording "to allow synchronization of slots" seemed misleading to
> me. Isn't that more the purpose of the 'enable_syncslot' GUC? I think
> the intended wording is more like below:
>
> SUGGESTION
> If slot synchronization is enabled then it is also necessary to
> specify <literal>dbname</literal> in the
> <varname>primary_conninfo</varname> string. This will only be used for
> slot synchronization. It is ignored for streaming.
>
> ======
> doc/src/sgml/logicaldecoding.sgml
>
> 7.
> +    <para>
> +     A logical replication slot on the primary can be synchronized to the hot
> +     standby by enabling the failover option during slot creation and set
> +     <varname>enable_syncslot</varname> on the standby. For the synchronization
> +     to work, it is mandatory to have physical replication slot between the
> +     primary and the standby. This physical replication slot for the standby
> +     should be listed in <varname>standby_slot_names</varname> on the primary
> +     to prevent the subscriber from consuming changes faster than the hot
> +     standby. Additionally, similar to creating a logical replication slot
> +     on the hot standby, <varname>hot_standby_feedback</varname> should be
> +     set on the standby and a physical slot between the primary and the standby
> +     should be used.
> +    </para>
>
>
> 7a.
> /creation and set/creation and setting/
> /to have physical replication/to have a physical replication/
>
> ~
>
> 7b.
> It's unclear why this is saying "should be listed in
> standby_slot_names" and "hot_standby_feedback should be set on the
> standby". Why is it saying "should" instead of MUST -- are these
> optional? I thought the GUC validation function mandates these (???).
>

standby_slot_names setting is not mandatory, it is recommended though.
OTOH hot_standby_feedback setting is mandatory. So I have changed
accordingly.

> ~
>
> 7c.
> Why does the paragraph say "and a physical slot between the primary
> and the standby should be used.";  isn't that exactly what was already
> written earlier ("For the synchronization to work, it is mandatory to
> have physical replication slot between the primary and the standby"
>

Removed the duplicate line.

> ~~~
>
> 8.
> +    <para>
> +     By enabling synchronization of slots, logical replication can be resumed
> +     after failover depending upon the
> +     <link linkend="view-pg-replication-slots">pg_replication_slots</link>.<structfield>sync_state</structfield>
> +     for the synchronized slots on the standby at the time of failover.
> +     The slots which were in ready sync_state ('r') on the standby before
> +     failover can be used for logical replication after failover. However,
> +     the slots which were in initiated sync_state ('i) and were not
> +     sync-ready ('r') at the time of failover will be dropped and logical
> +     replication for such slots can not be resumed after failover. This applies
> +     to the case where a logical subscription is disabled before
> failover and is
> +     enabled after failover. If the synchronized slot due to disabled
> +     subscription could not be made sync-ready ('r') on standby, then the
> +     subscription can not be resumed after failover even when enabled.
>
>
> 8a.
> This feels overcomplicated -- too much information?
>
> SUGGESTION
> depending upon the ... sync_state for the synchronized slots on the
> standby at the time of failover. Only slots that were in ready
> sync_state ('r') on the standby before failover can be used for
> logical replication after failover
>
> ~~~
>
> 8b.
> +     the slots which were in initiated sync_state ('i) and were not
> +     sync-ready ('r') at the time of failover will be dropped and logical
> +     replication for such slots can not be resumed after failover. This applies
> +     to the case where a logical subscription is disabled before
> failover and is
> +     enabled after failover. If the synchronized slot due to disabled
> +     subscription could not be made sync-ready ('r') on standby, then the
> +     subscription can not be resumed after failover even when enabled.
>
> But isn't ALL that part pretty much redundant information for the
> user? I thought these are not ready state, so they are not usable...
> End-Of-Story. Isn't everything else just more like implementation
> details, which the user does not need to know about?
>

 'sync_state' is a way to monitor the state of synchronization and I
feel it is important to tell what happens with 'i' state slots. Also
there was a comment to add this info in doc that disabled
subscriptions are not guaranteed to be usable if enabled after
failover. Thus it was added and rest of the info forms a base for
that. We can trim down or rephrase if needed.

> ~~~
>
> 9.
> +     If the primary is idle, making the synchronized slot on the standby
> +     as sync-ready ('r') for enabled subscription may take noticeable time.
> +     This can be sped up by calling the
> +     <function>pg_log_standby_snapshot</function> function on the primary.
> +    </para>
>
> SUGGESTION
> If the primary is idle, then the synchronized slots on the standby may
> take a noticeable time to reach the ready ('r') sync_state. This can
> be sped up by calling the
> <function>pg_log_standby_snapshot</function> function on the primary.
>
> ======
> doc/src/sgml/system-views.sgml
>
> 10.
> +
> +     <row>
> +      <entry role="catalog_table_entry"><para role="column_definition">
> +       <structfield>sync_state</structfield> <type>char</type>
> +      </para>
> +      <para>
> +      Defines slot synchronization state. This is meaningful on the physical
> +      standby which has enabled slots synchronization.
> +      </para>
>
> I felt that this part "which has enabled slots synchronization" should
> cross-reference to the 'sync_enabled' GUC.
>
> ~~~
>
> 11.
> +      <para>
> +       State code:
> +       <literal>n</literal> = none for user created slots,
> +       <literal>i</literal> = sync initiated for the slot but slot is not ready
> +        yet for periodic syncs,
> +       <literal>r</literal> = ready for periodic syncs.
> +      </para>
>
> I'm wondering why don't we just reuse 'd' (disabled), 'p' (pending),
> 'e' (enabled) like the other tri-state attributes are using.
>

I think it is not a property of a slot where we say enabled/disabled.
It is more like an operation and thus initiated, ready etc sounds
better. These states are similar to the ones maintained for table-sync
operation (SUBREL_STATE_INIT, SUBREL_STATE_READY etc)

>
> 12.
> +      <para>
> +      The hot standby can have any of these sync_state for the slots but on a
> +      hot standby, the slots with state 'r' and 'i' can neither be
> used for logical
> +      decoded nor dropped by the user. The primary server will have sync_state
> +      as 'n' for all the slots. But if the standby is promoted to become the
> +      new primary server, sync_state can be seen 'r' as well. On this new
> +      primary server, slots with sync_state as 'r' and 'n' will
> behave the same.
> +      </para></entry>
> +     </row>
>
> 12a.
> /logical decoded/logical decoding/
>
> ~
>
> 12b.
> "sync_state as 'r' and 'n' will behave the same" sounds kind of hacky.
> Is there no alternative?
>

I am reviewing your suggestion on 'r' to 'n' conversion on promotion
given later in this email. So give me some more time.

> Anyway, IMO mentioning about primary server states seems overkill,
> because you already said "This is meaningful on the physical standby"
> which I took as implying that it is *not* meaningful from the POV of
> the primary server.
>

In case we planned to retain 'r', it then makes sense to document that
the sync_state on primary can also be 'r' if the primary was promoted
from a standby, because this is a special case which the user may not
be aware of.

> In light of this, I'm wondering if a better name for this attribute
> would be: 'standby_sync_state'
>

sync_state has some value for primary too. It is not null on primary.
Thus the current name seems a better choice.

> ======
> src/backend/access/transam/xlogrecovery.c
>
> 13.
> + /*
> + * Shutdown the slot sync workers to prevent potential conflicts between
> + * user processes and slotsync workers after a promotion. Additionally,
> + * drop any slots that have initiated but not yet completed the sync
> + * process.
> + */
> + ShutDownSlotSync();
> + slotsync_drop_initiated_slots();
> +
>
> Is this where maybe the 'sync_state' should also be updated for
> everything so you are not left with confusion about different states
> on a node that is no longer a standby node?
>

yes, this is the place. But this needs more thought as it may cause
too much disk activity during promotion. so let me analyze and come
back.

> ======
> src/backend/postmaster/postmaster.c
>
> 14. PostmasterMain
>
>   ApplyLauncherRegister();
>
> + SlotSyncWorkerRegister();
> +
>
> Every other function call here is heavily commented but there is a
> conspicuous absence of a comment here.
>

Added some comments, but not very confident on those, so let me know.

> ~~~
>
> 15. bgworker_should_start_now
>
>   if (start_time == BgWorkerStart_ConsistentState)
>   return true;
> + else if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
> + pmState != PM_RUN)
> + return true;
>   /* fall through */
> Change "else if" to "if" would be simpler.
>
> ======
> .../libpqwalreceiver/libpqwalreceiver.c
>
> 16.
> + for (opt = opts; opt->keyword != NULL; ++opt)
> + {
> + /*
> + * If multiple dbnames are specified, then the last one will be
> + * returned
> + */
> + if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
> + opt->val[0] != '\0')
> + dbname = pstrdup(opt->val);
> + }
>
> This can use a tidier C99 style to declare 'opt' as the loop variable.
>
> ~~~
>
> 17.
>  static void
>  libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
> - bool failover)
> + bool failover)
>
> What is this change for? Or, if something is wrong with the indent
> then anyway it should be fixed in patch 0001.
>

yes, it should go to patch01. Done.

> ======
> src/backend/replication/logical/logical.c
>
> 18.
>
> + /*
> + * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
> + * promotion.
> + */
> + if (!RecoveryInProgress() && slot->data.sync_state ==
> SYNCSLOT_STATE_INITIATED)
> + elog(ERROR, "replication slot \"%s\" was not synced completely from
> the primary server",
> + NameStr(slot->data.name));
> +
> + /*
> + * Do not allow consumption of a "synchronized" slot until the standby
> + * gets promoted.
> + */
> + if (RecoveryInProgress() && slot->data.sync_state != SYNCSLOT_STATE_NONE)
> + ereport(ERROR,
> + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> + errmsg("cannot use replication slot \"%s\" for logical decoding",
> + NameStr(slot->data.name)),
> + errdetail("This slot is being synced from the primary server."),
> + errhint("Specify another replication slot.")));
> +
>
> 18a.
>
> Instead of having !RecoveryInProgress() and RecoveryInProgress() in
> separate conditions is the code simpler like:
>
> SUGGESTION
>
> if (RecoveryInProgress())
> {
>   /* Do not allow ... */
>   if (slot->data.sync_state != SYNCSLOT_STATE_NONE) ...
> }
> else
> {
>   /* Slots in state... */
>   if (slot->data.sync_state == SYNCSLOT_STATE_INITIATED) ...
> }
>
> ~
>
> 18b.
> Should the errdetail give the current state?
>

I think it is not needed, current info looks good enough. User can
always use pg_replication_slots to monitor sync_state info.

> ======
> src/backend/replication/logical/slotsync.c
>
> 19.
> +/*
> + * Number of attempts for wait_for_primary_slot_catchup() after
> + * which it aborts the wait and the slot sync worker then moves
> + * to the next slot creation/sync.
> + */
> +#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
>
> Given this is only used within one static function, I'm wondering if
> it would be tidier to also move this macro to within that function.
>
> ~~~
>
> 20. wait_for_primary_slot_catchup
>
> +/*
> + * Wait for remote slot to pass locally reserved position.
> + *
> + * Ping and wait for the primary server for
> + * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
> + * does not catch up, abort the wait. The ones for which wait is aborted will
> + * attempt the wait and sync in the next sync-cycle.
> + *
> + * *persist will be set to false if the slot has disappeared or was invalidated
> + * on the primary; otherwise, it will be set to true.
> + */
>
> 20a.
> The comment doesn't say the meaning of the boolean returned.
>
> ~
>
> 20b.
> /*persist will be set/If passed, *persist will be set/
>
> ~~~
>
> 21.
> + appendStringInfo(&cmd,
> + "SELECT conflicting, restart_lsn, confirmed_flush_lsn,"
> + " catalog_xmin FROM pg_catalog.pg_replication_slots"
> + " WHERE slot_name = %s",
> + quote_literal_cstr(remote_slot->name));
>
> Somehow, I felt it is more readable if the " FROM" starts on a new line.
>
> e.g.
> "SELECT conflicting, restart_lsn, confirmed_flush_lsn, catalog_xmin"
> " FROM pg_catalog.pg_replication_slots"
> " WHERE slot_name = %s"
>
> ~~~
>
> 22.
> + ereport(ERROR,
> + (errmsg("could not fetch slot info for slot \"%s\" from the"
> + " primary server: %s",
> + remote_slot->name, res->err)));
>
> Perhaps the message can be shortened like:
> "could not fetch slot \"%s\" info from the primary server: %s"
>
> ~~~
>
> 23.
> + ereport(WARNING,
> + (errmsg("slot \"%s\" disappeared from the primary server,"
> + " slot creation aborted", remote_slot->name)));
>
> Would this be better split into parts?
>
> SUGGESTION
> errmsg "slot \"%s\" creation aborted"
> errdetail "slot was not found on the primary server"
>
> ~~~
>
> 24.
> + ereport(WARNING,
> + (errmsg("slot \"%s\" invalidated on the primary server,"
> + " slot creation aborted", remote_slot->name)));
>
> (similar to previous)
>
> SUGGESTION
> errmsg "slot \"%s\" creation aborted"
> errdetail "slot was invalidated on the primary server"
>
> ~~~
>
> 25.
> + /*
> + * Once we got valid restart_lsn, then confirmed_lsn and catalog_xmin
> + * are expected to be valid/non-null.
> + */
>
> SUGGESTION
> Having got a valid restart_lsn, the confirmed_lsn and catalog_xmin are
> expected to be valid/non-null.
>
> ~~~
>
> 26. slotsync_drop_initiated_slots
>
> +/*
> + * Drop the slots for which sync is initiated but not yet completed
> + * i.e. they are still waiting for the primary server to catch up.
> + */
>
> I found "waiting for the primary server to catch up" to be difficult
> to understand without knowing the full details, but it is not really
> described properly until a much larger comment that is buried in the
> synchronize_one_slot(). So I think all this needs explanation up-front
> in the file, which you can refer to. I have repeated this same review
> comment in a couple of places.
>

I have updated header of file with details and gave reference here and
all such similar places.

> ~~~
>
> 27. get_local_synced_slot_names
>
> +static List *
> +get_local_synced_slot_names(void)
> +{
> + List    *localSyncedSlots = NIL;
>
> 27a.
> It's not returning a list of "names" though, so is this an appropriate
> function name?
>
> ~~~
>
> 27b.
> Suggest just call that ('localSyncedSlots') differently.
> - In slotsync_drop_initiated_slots() function they are just called 'slots'
> - In drop_obsolete_slots() function it is called 'local_slot_list'
>
> IMO it is better if all these are consistently named -- just all lists
> 'slots' or all 'local_slots' or whatever.
>
> ~~~
>
> 28. check_sync_slot_validity
>
> +static bool
> +check_sync_slot_validity(ReplicationSlot *local_slot, List *remote_slots,
> + bool *locally_invalidated)
>
> Somehow this wording "validity" seems like a misleading function name,
> because the return value has nothing to do with the slot field
> invalidated.
>
> The validity/locally_invalidated stuff is a secondary return as a side
> effect for the "true" case.
>
> A more accurate function name would be more like check_sync_slot_on_remote().
>
> ~~~
>
> 29. check_sync_slot_validity
>
> +static bool
> +check_sync_slot_validity(ReplicationSlot *local_slot, List *remote_slots,
> + bool *locally_invalidated)
> +{
> + ListCell   *cell;
>
> There is inconsistent naming --
>
> ListCell lc; ListCell cell; ListCell lc_slot; etc..
>
> IMO the more complicated names aren't of much value -- probably
> everything can be changed to 'lc' for consistency.
>
> ~~~
>
> 30. drop_obsolete_slots
>
> + /*
> + * Get the list of local 'synced' slot so that those not on remote could
> + * be dropped.
> + */
>
> /slot/slots/
>
> Also, I don't think it is necessary to say "so that those not on
> remote could be dropped." -- That is already described in the function
> comment and again in a comment later in the loop. That seems enough.
> If the function name get_local_synced_slot_names() is improved a bit
> the comment seems redundant because it is obvious from the function
> name.
>
> ~~~
>
> 31.
> + foreach(lc_slot, local_slot_list)
> + {
> + ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc_slot);
> + bool local_exists = false;
> + bool locally_invalidated = false;
> +
> + local_exists = check_sync_slot_validity(local_slot, remote_slot_list,
> + &locally_invalidated);
>
> Shouldn't that 'local_exists' variable be called 'remote_exists'?
> That's what the other comments seem to be saying.
>
> ~~~
>
> 32. construct_slot_query
>
> + appendStringInfo(s,
> + "SELECT slot_name, plugin, confirmed_flush_lsn,"
> + " restart_lsn, catalog_xmin, two_phase, failover,"
> + " database, pg_get_slot_invalidation_cause(slot_name)"
> + " FROM pg_catalog.pg_replication_slots"
> + " WHERE failover and sync_state != 'i'");
>
> Just wondering if substituting the SYNCSLOT_STATE_INITIATED constant
> here might be more appropriate than hardwiring 'i'. Why have a
> constant but not use it?
>

On hold. I could not find quote_* function for a character just like
we have 'quote_literal_cstr' for string. Will review. Let me know if
you know.

> ~~~
>
> 33. synchronize_one_slot
>
> +static void
> +synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
> + bool *slot_updated)
> +{
> + ReplicationSlot *s;
> + char sync_state = 0;
>
> 33a.
> It seems strange that the sync_state is initially assigned something
> other than the 3 legal values. Should this be defaulting to
> SYNCSLOT_STATE_NONE instead?
>

No, that will change the flow. It should stay uninitialized if the
slot is not found. I have changed assignment to '\0' for better
clarity.

> ~
>
> 33b.
> I think it is safer to default the *slot_updated = false; because the
> code appears to assume it was false already which may or may not be
> true.
>

It is initialized to false in the caller, so we are good here.

> ~~~
>
> 34.
> + /*
> + * Make sure that concerned WAL is received before syncing slot to target
> + * lsn received from the primary server.
> + *
> + * This check should never pass as on the primary server, we have waited
> + * for the standby's confirmation before updating the logical slot.
> + */
>
> Maybe this comment should mention up-front that it is just a "Sanity check:"
>
> ~~~
>
> 35.
> + /*
> + * With hot_standby_feedback enabled and invalidations handled
> + * apropriately as above, this should never happen.
> + */
> + if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn)
> + {
> + ereport(ERROR,
> + errmsg("not synchronizing local slot \"%s\" LSN(%X/%X)"
> +    " to remote slot's LSN(%X/%X) as synchronization "
> +    " would move it backwards", remote_slot->name,
> +    LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
> +    LSN_FORMAT_ARGS(remote_slot->restart_lsn)));
> +
> + goto cleanup;
> + }
>
> 35a.
> IIUC then this another comment that should say it is just a "Sanity-check:".
>
> ~
>
> 35b.
> I was wondering if there should be Assert(hot_standby_feedback) here
> also. The comment "With hot_standby_feedback enabled" is a bit vague
> whereas including an Assert will clarify that it must be set.
>

 I think assert is not needed. Slot-sync worker will never start if
hot_standby_feedback is disabled. If we put assert here, we need to
assert at all other places too where we use other related GUCs like
primary_slot_name, conn_info etc.

> ~
>
> 35c.
> Since it says "this should never happen" then it appears elog is more
> appropriate than ereport because translations are not needed, right?
>
> ~
>
> 35d.
> The ERROR will make that goto cleanup unreachable, won't it?
>
> ~~~
>
> 36.
> + /*
> + * Already existing slot but not ready (i.e. waiting for the primary
> + * server to catch-up), lets attempt to make it sync-ready now.
> + */
>
> /lets/let's/
>
> ~~~
>
> 37.
> + /*
> + * Refer the slot creation part (last 'else' block) for more details
> + * on this wait.
> + */
> + if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
> + TransactionIdPrecedes(remote_slot->catalog_xmin,
> +   MyReplicationSlot->data.catalog_xmin))
> + {
> + if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
> + {
> + goto cleanup;
> + }
> + }
>
> 37a.
> Having to jump forward to understand earlier code seems backward. IMO
> there should be a big comment atop this module about this subject
> which the comment here can just refer to. I will write more about this
> topic later (below).
>
> ~
>
> 37b.
> The extra code curly braces are not needed.
>
> ~~~
>
> 38.
> + ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready "
> + "now", remote_slot->name));
>
> Better to put the whole errmsg() on a newline instead of splitting the
> string like that.
>
> ~~~
>
> 39.
> + /* User created slot with the same name exists, raise ERROR. */
> + else if (sync_state == SYNCSLOT_STATE_NONE)
> + {
> + ereport(ERROR,
> + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> + errmsg("skipping sync of slot \"%s\" as it is a user created"
> + " slot", remote_slot->name),
> + errdetail("This slot has failover enabled on the primary and"
> +    " thus is sync candidate but user created slot with"
> +    " the same name already exists on the standby")));
> + }
>
> I felt it would be better to eliminate this case immediately up-front
> when you first searched for the slot names. e.g. code like below. IIUC
> this refactor also means the default sync_state can be assigned a
> normal value (as I suggested above) instead of the strange assignment
> to 0.

 I feel NULL character ('\0') is better default for local variable
sync_slot as we specifically wanted it to be NULL if not assigned.
Assigning it to 'SYNCSLOT_STATE_NONE' will be misleading. But moved
the 'SYNCSLOT_STATE_NONE' related error though.

>
> + /* Search for the named slot */
> + if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
> + {
> + SpinLockAcquire(&s->mutex);
> + sync_state = s->data.sync_state;
> + SpinLockRelease(&s->mutex);
>
> INSERT HERE
> +     /* User-created slot with the same name exists, raise ERROR. */
> +     if (sync_state == SYNCSLOT_STATE_NONE)
> +     ereport(ERROR, ...
> + }
>
> ~~~
>
> 40.
> + /* Otherwise create the slot first. */
> + else
> + {
>
> Insert a blank line above that comment for better readability (same as
> done for earlier 'else' in this same function)
>
> ~~~
>
> 41.
> + ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
> +   remote_slot->two_phase,
> +   remote_slot->failover,
> +   SYNCSLOT_STATE_INITIATED);
> +
> + slot = MyReplicationSlot;
>
> In hindsight, the prior if/else code blocks in this function also
> could have done "slot = MyReplicationSlot;" same as this -- then the
> code would be much less verbose.
>
> ~~~
>
> 42.
> + SpinLockAcquire(&slot->mutex);
> + slot->data.database = get_database_oid(remote_slot->database, false);
> +
> + namestrcpy(&slot->data.plugin, remote_slot->plugin);
> + SpinLockRelease(&slot->mutex);
>
> IMO the code would be more readable *without* a blank line here
> because the mutexed block is more obvious.
>
> ~~~
>
> 43.
> + /*
> + * If the local restart_lsn and/or local catalog_xmin is ahead of
> + * those on the remote then we cannot create the local slot in sync
> + * with the primary server because that would mean moving the local
> + * slot backwards and we might not have WALs retained for old LSN. In
> + * this case we will wait for the primary server's restart_lsn and
> + * catalog_xmin to catch up with the local one before attempting the
> + * sync.
> + */
>
> 43a.
> This comment describes some fundamental concepts about how this logic
> works. I felt this and other comments like this should be at the top
> of this slotsync.c file. Then anything that needs to mention about it
> can refer to the top comment. For example, I also found other comments
> like "... they are still waiting for the primary server to catch up."
> to be difficult to understand without knowing these details, but I
> think describing core design stuff up-front and saying "refer to the
> comment atop the fil" probably would help a lot.
>
> ~
>
> 43b.
> Should "wait for the primary server's restart_lsn and..." be "wait for
> the primary server slot's restart_lsn and..." ?
>
> ~~~
>
> 44.
> + {
> + bool persist;
> +
> + if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &persist))
> + {
> + /*
> + * The remote slot didn't catch up to locally reserved
> + * position.
> + *
> + * We do not drop the slot because the restart_lsn can be
> + * ahead of the current location when recreating the slot in
> + * the next cycle. It may take more time to create such a
> + * slot. Therefore, we persist it (provided remote-slot is
> + * still valid) and attempt the wait and synchronization in
> + * the next cycle.
> + */
> + if (persist)
> + {
> + ReplicationSlotPersist();
> + *slot_updated = true;
> + }
> +
> + goto cleanup;
> + }
> + }
>
> Looking at the way this 'persist' parameter is used I felt is it too
> complicated. IIUC the wait_for_primary_slot_catchup can only return
> *persist = true (for a false return) when it has reached/exceeded the
> number of retries and still not yet caught up. Why should
> wait_for_primary_slot_catchup() pretend to know about persistence?
>
> In other words, I thought a more meaningful parameter/variable name
> (instead of 'persist') is something like 'wait_attempts_exceeded'. IMO
> that will make wait_for_primary_slot_catchup() code easier, and here
> you can just say like below, where the code matches the comment
> better. Thoughts?
>
> + if (wait_attempts_exceeded)
> + {
> + ReplicationSlotPersist();
> + *slot_updated = true;
> + }
>

yes, it will make code simpler. Changed it.

> ~~~
>
> 45.
> +
> +
> + /*
> + * Wait for primary is either not needed or is over. Update the lsns
> + * and mark the slot as READY for further syncs.
> + */
>
> Double blank lines?
>
> ~~~
>
> 46.
> + ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready "
> + "now", remote_slot->name));
> + }
> +
> +cleanup:
>
> Better to put the whole errmsg() on a newline instead of splitting the
> string like that.
>
> ~~~
>
> 47. synchronize_slots
>
> +/*
> + * Synchronize slots.
> + *
> + * Gets the failover logical slots info from the primary server and update
> + * the slots locally. Creates the slots if not present on the standby.
> + *
> + * Returns nap time for the next sync-cycle.
> + */
> +static long
> +synchronize_slots(WalReceiverConn *wrconn)
>
> /update/updates/
>
> ~~~
>
> 48.
> + /* The primary_slot_name is not set yet or WALs not received yet */
> + SpinLockAcquire(&WalRcv->mutex);
> + if (!WalRcv ||
> + (WalRcv->slotname[0] == '\0') ||
> + XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
> + {
> + SpinLockRelease(&WalRcv->mutex);
> + return naptime;
> + }
> + SpinLockRelease(&WalRcv->mutex);
>
> Just wondering if the scenario of "WALS not received" is a bit more
> like "no activity" so perhaps the naptime returned should be
> WORKER_INACTIVITY_NAPTIME_MS here?
>

This may happen if walreceiver is temporarily having some issue.
Longer nap is not recommended here. We should check the state again
after a short nap.

>
> ~~~
>
> 49.
> + /* Construct query to get slots info from the primary server */
> + initStringInfo(&s);
> + construct_slot_query(&s);
>
> I did not like the construct_slot_query() to be separated from this
> function because it makes it too difficult to see if the slot_attr
> numbers and column types in this function are correct w.r.t. that
> query. IMO better when everything is in the same place where you can
> see it all together. e.g. Less risk of breaking something if changes
> are made.
>
> ~~~
>
> 50.
> + /* Construct the remote_slot tuple and synchronize each slot locally */
> + slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
>
> Normally in all the other functions the variable 'slot' was the local
> ReplicationSlot but IIUC here represents a remote tuple. Making a
> different name would be better like 'remote_slottup' or something
> else.
>

Have changed it to 'tupslot' to keep it short but different from slot.

> ~~~
>
> 51.
> + /*
> + * If any of the slots get updated in this sync-cycle, retain default
> + * naptime and update 'last_update_time' in slot sync worker. But if no
> + * activity is observed in this sync-cycle, then increase naptime provided
> + * inactivity time reaches threshold.
> + */
>
> I think "retain" is a slightly wrong word here because it might have
> been WORKER_INACTIVITY_NAPTIME_MS in the previous cycle.
>
> Maybe just /retain/use/
>
> ~~~
>
> 52.
> +/*
> + * Connects primary to validate the slot specified in primary_slot_name.
> + *
> + * Exits the worker if physical slot with the specified name does not exist.
> + */
> +static void
> +validate_primary_slot(WalReceiverConn *wrconn)
>
> There is already a connection, so not sure if this connect should be
> saying "connects to"; Maybe is should be saying more like below:
>
> SUGGESTION
> Using the specified primary server connection, validate if the
> physical slot identified by GUC primary_slot_name exists.
>
> Exit the worker if the slot is not found.
>
> ~~~
>
> 53.
> + initStringInfo(&cmd);
> + appendStringInfo(&cmd,
> + "select count(*) = 1 from pg_replication_slots where "
> + "slot_type='physical' and slot_name=%s",
> + quote_literal_cstr(PrimarySlotName));
>
> Write the SQL keywords in uppercase.
>
> ~~~
>
> 54.
> + if (res->status != WALRCV_OK_TUPLES)
> + ereport(ERROR,
> + (errmsg("could not fetch primary_slot_name info from the "
> + "primary: %s", res->err)));
>
> Shouldn't the name of the unfound slot be shown in the ereport, or
> will that already appear in the res->err?
>
> ~~~
>
> 55.
> + ereport(ERROR,
> + errmsg("exiting slots synchronization as slot specified in "
> +    "primary_slot_name is not valid"));
> +
>
> IMO the format should be the same as I suggested (later) for all the
> validate_slotsync_parameters() errors.
>
> Also, I think the name of the unfound slot needs to be in this message.
>
> So maybe result is like this:
>
> SUGGESTION
>
> ereport(ERROR,
>   errmsg("exiting from slot synchronization due to bad configuration")
>   /* translator: second %s is a GUC variable name */
>   errhint("The primary slot \"%s\" specified by %s is not valid.",
> slot_name, "primary_slot_name")
> );
>
> ~~~
>
> 56.
> +/*
> + * Checks if GUCs are set appropriately before starting slot sync worker
> + */
> +static void
> +validate_slotsync_parameters(char **dbname)
> +{
> + /*
> + * Since 'enable_syncslot' is ON, check that other GUC settings
> + * (primary_slot_name, hot_standby_feedback, wal_level, primary_conninfo)
> + * are compatible with slot synchronization. If not, raise ERROR.
> + */
> +
>
> 56a.
> I thought that 2nd comment sort of belonged in the function comment.
>
> ~
>
> 56b.
> It says "Since 'enable_syncslot' is ON", but I IIUC that is wrong
> because the other function slotsync_reread_config() might detect a
> change in this GUC and cause this validate_slotsync_parameters() to be
> called when enable_syncslot was changed to false.
>
> In other words, I think you also need to check 'enable_syncslot' and
> exit with appropriate ERROR same as all the other config problems.
>
> OTOH if this is not possible, then the slotsync_reread_config() might
> need fixing instead.
>

'enable_syncslot' is recently changed to PGC_POSTMASTER from
PGC_SIGHUP. Thus 'slotsync_reread_config' also needs to get rid of
'enable_syncslot'. I have changed that now. Slightly changed the
comment as well.

> ~~~
>
> 57.
> + /*
> + * A physical replication slot(primary_slot_name) is required on the
> + * primary to ensure that the rows needed by the standby are not removed
> + * after restarting, so that the synchronized slot on the standby will not
> + * be invalidated.
> + */
> + if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
> + ereport(ERROR,
> + errmsg("exiting slots synchronization as primary_slot_name is "
> +    "not set"));
> +
> + /*
> + * Hot_standby_feedback must be enabled to cooperate with the physical
> + * replication slot, which allows informing the primary about the xmin and
> + * catalog_xmin values on the standby.
> + */
> + if (!hot_standby_feedback)
> + ereport(ERROR,
> + errmsg("exiting slots synchronization as hot_standby_feedback "
> +    "is off"));
> +
> + /*
> + * Logical decoding requires wal_level >= logical and we currently only
> + * synchronize logical slots.
> + */
> + if (wal_level < WAL_LEVEL_LOGICAL)
> + ereport(ERROR,
> + errmsg("exiting slots synchronisation as it requires "
> +    "wal_level >= logical"));
> +
> + /*
> + * The primary_conninfo is required to make connection to primary for
> + * getting slots information.
> + */
> + if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
> + ereport(ERROR,
> + errmsg("exiting slots synchronization as primary_conninfo "
> +    "is not set"));
> +
> + /*
> + * The slot sync worker needs a database connection for walrcv_exec to
> + * work.
> + */
> + *dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
> + if (*dbname == NULL)
> + ereport(ERROR,
> + errmsg("exiting slots synchronization as dbname is not "
> +    "specified in primary_conninfo"));
> +
> +}
>
> IMO all these errors can be improved by:
> - using a common format
> - including errhint for the reason
> - using the same tone for instructions on what to do (e.g saying must
> be set, rather than what was not set)
>
> SUGGESTION (something like this)
>
> ereport(ERROR,
>   errmsg("exiting from slot synchronization due to bad configuration")
>   /* translator: %s is a GUC variable name */
>   errhint("%s must be defined.", "primary_slot_name")
> );
>
> ereport(ERROR,
>   errmsg("exiting from slot synchronization due to bad configuration")
>   /* translator: %s is a GUC variable name */
>   errhint("%s must be enabled.", "hot_standby_feedback")
> );
>
> ereport(ERROR,
>   errmsg("exiting from slot synchronization due to bad configuration")
>   /* translator: wal_level is a GUC variable name, 'logical' is a value */
>   errhint("wal_level must be >= logical.")
> );
>
> ereport(ERROR,
>   errmsg("exiting from slot synchronization due to bad configuration")
>   /* translator: %s is a GUC variable name */
>   errhint("%s must be defined.", "primary_conninfo")
> );
>
> ereport(ERROR,
>   errmsg("exiting from slot synchronization due to bad configuration")
>   /* translator: 'dbname' is a specific option; %s is a GUC variable name */
>   errhint("'dbname' must be specified in %s.", "primary_conninfo")
> );
>
> ~~~
>
> 58.
> + *dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
> + if (*dbname == NULL)
> + ereport(ERROR,
> + errmsg("exiting slots synchronization as dbname is not specified in
> primary_conninfo"));
> +
> +}
>
> Unnecessary blank line at the end of the function
>
> ~~~
>
> 59.
> +/*
> + * Re-read the config file.
> + *
> + * If any of the slot sync GUCs changed, validate the values again
> + * through validate_slotsync_parameters() which will exit the worker
> + * if validaity fails.
> + */
>
> SUGGESTION
> If any of the slot sync GUCs have changed, re-validate them. The
> worker will exit if the check fails.
>
> ~~~
>
> 60.
> + char    *conninfo = pstrdup(PrimaryConnInfo);
> + char    *slotname = pstrdup(PrimarySlotName);
> + bool syncslot = enable_syncslot;
> + bool standbyfeedback = hot_standby_feedback;
>
> For clarity, I would have used var names to match the old GUCs.
>
> e.g.
> /conninfo/old_primary_conninfo/
> /slotname/old_primary_slot_name/
> /syncslot/old_enable_syncslot/
> /standbyfeedback/old_hot_standby_feedback/
>
> ~~~
>
> 61.
> + dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
> + Assert(dbname);
>
> This code seems premature. IIUC this is only needed to detect that the
> dbname was changed. But I think the prerequisite is first that the
> conninfoChanged is true. So really this code should be guarded by if
> (conninfoChanged) so it can be done later in the function.
>

Once PrimaryConnInfo is changed, we can not get old-dbname. So it is
required to be done before we reach 'conninfoChanged'

> ~~~
>
> 62.
> + if (conninfoChanged || slotnameChanged ||
> + (syncslot != enable_syncslot) ||
> + (standbyfeedback != hot_standby_feedback))
> + {
> + revalidate = true;
> + }
>
> SUGGESTION
>
> revalidate = conninfoChanged || slotnameChanged ||
>  (syncslot != enable_syncslot) ||
>  (standbyfeedback != hot_standby_feedback);
>
> ~~~
>
> 63.
> + /*
> + * Since we have initialized this worker with old dbname, thus exit if
> + * dbname changed. Let it get restarted and connect to new dbname
> + * specified.
> + */
> + if (conninfoChanged && strcmp(dbname, new_dbname) != 0)
> + {
> + ereport(ERROR,
> + errmsg("exiting slot sync woker as dbname in "
> +    "primary_conninfo changed"));
> + }
>
> 63a.
> /old dbname/the old dbname/
> /new dbname/the new dbname/
> /woker/worker/
>
> ~
>
> 63b.
> This code feels awkward. Can't this dbname check and accompanying
> ERROR message be moved down into validate_slotsync_parameters(), so it
> lives along with all the other GUC validation logic? Maybe you'll need
> to change the validate_slotsync_parameters() parameters slightly but I
> think it is much better to keep all the validation together.
>
> ~~~
>
> 64.
> +
> +
> +/*
> + * Interrupt handler for main loop of slot sync worker.
> + */
> +static void
> +ProcessSlotSyncInterrupts(WalReceiverConn **wrconn)
>
> Double blank lines.
>
> ~~~
>
> 65.
> +
> +
> + if (ConfigReloadPending)
> + slotsync_reread_config();
> +}
>
> Double blank lines
>
> ~~~
>
> 66. slotsync_worker_onexit
>
> +static void
> +slotsync_worker_onexit(int code, Datum arg)
> +{
> + SpinLockAcquire(&SlotSyncWorker->mutex);
> + SlotSyncWorker->pid = 0;
> + SpinLockRelease(&SlotSyncWorker->mutex);
> +}
>
> Should assignment use InvalidPid (-1) instead of 0?
>
> ~~~
>
> 67. ReplSlotSyncWorkerMain
>
> + SpinLockAcquire(&SlotSyncWorker->mutex);
> +
> + Assert(SlotSyncWorker->pid == 0);
> +
> + /* Advertise our PID so that the startup process can kill us on promotion */
> + SlotSyncWorker->pid = MyProcPid;
> +
> + SpinLockRelease(&SlotSyncWorker->mutex);
>
> Shouldn't pid start as InvalidPid (-1) instead of Assert 0?
>
> ~~~
>
> 68.
> + /* Connect to the primary server */
> + wrconn = remote_connect();
> +
> + /*
> + * Connect to primary and validate the slot specified in
> + * primary_slot_name.
> + */
> + validate_primary_slot(wrconn);
>
> Maybe needs some slight rewording in the 2nd comment. "Connect to
> primary server" is already said and done in the 1st part.
>
> ~~~
>
> 69. IsSlotSyncWorker
>
> +/*
> + * Is current process the slot sync worker?
> + */
> +bool
> +IsSlotSyncWorker(void)
> +{
> + return SlotSyncWorker->pid == MyProcPid;
> +}
>
> 69a.
> For consistency with others like it, I thought this be called
> IsLogicalSlotSyncWorker().
>
> ~
>
> 69b.
> For consistency with the others like this, I think the extern should
> be declared in logicalworker.h
>
> ~~~
>
> 70. ShutDownSlotSync
>
> + SpinLockAcquire(&SlotSyncWorker->mutex);
> + if (!SlotSyncWorker->pid)
> + {
> + SpinLockRelease(&SlotSyncWorker->mutex);
> + return;
> + }
>
> IMO should be comparing with InvalidPid (-1) here; not 0.
>
> ~~~
>
> 71.
> + SpinLockAcquire(&SlotSyncWorker->mutex);
> +
> + /* Is it gone? */
> + if (!SlotSyncWorker->pid)
> + break;
> +
> + SpinLockRelease(&SlotSyncWorker->mutex);
>
> Ditto. bad pids should be InvalidPid (-1), not 0.
>
> ~~~
>
> 72. SlotSyncWorkerShmemInit
>
> + if (!found)
> + {
> + memset(SlotSyncWorker, 0, size);
> + SpinLockInit(&SlotSyncWorker->mutex);
> + }
>
> Probably here the unassigned pid should be set to InvalidPid (-1), not 0.
>
> ~~~
>
> 73. SlotSyncWorkerRegister
>
> + if (!enable_syncslot)
> + {
> + ereport(LOG,
> + errmsg("skipping slots synchronization as enable_syncslot is "
> +    "disabled."));
> + return;
> + }
>
> /as/because/
>
> ======
> src/backend/replication/logical/tablesync.c
>
> 74.
>  #include "commands/copy.h"
> +#include "commands/subscriptioncmds.h"
>  #include "miscadmin.h"
>
> There were only #include changes but no code changes. Is the #include needed?
>
> ======
> src/backend/replication/slot.c

There is some change in way headers are included. I need to review it
in detail. Keeping it on hold. I tried to explain few points on this
in [1] (see last comment)
[1]: https://www.postgresql.org/message-id/CAJpy0uD6dWUvBgy8MGdugf_Am4pLXTL_vqcwSeHO13v%2BMzc9KA%40mail.g...

>
> 75. ReplicationSlotCreate
>
>  void
>  ReplicationSlotCreate(const char *name, bool db_specific,
>     ReplicationSlotPersistency persistency,
> -   bool two_phase, bool failover)
> +   bool two_phase, bool failover, char sync_state)
>
> The function comment goes to trouble to describe all the parameters
> except for 'failover' and 'sync_slate'. I think a failover comment
> should be added in patch 0001 and then the sync_state comment should
> be added in patch 0002.
>
> ~~~
>
> 76.
> + /*
> + * Do not allow users to drop the slots which are currently being synced
> + * from the primary to the standby.
> + */
> + if (user_cmd && RecoveryInProgress() &&
> + MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
> + {
> + ReplicationSlotRelease();
> + ereport(ERROR,
> + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> + errmsg("cannot drop replication slot \"%s\"", name),
> + errdetail("This slot is being synced from the primary.")));
> + }
>
> Should the errdetail give the current state?
>

I feel current info looks good. User can always use
pg_replication_slots to monitor sync_state info.

>
> ======
> src/backend/tcop/postgres.c
>
> 77.
> + else if (IsSlotSyncWorker())
> + {
> + ereport(DEBUG1,
> + (errmsg_internal("replication slot sync worker is shutting down due
> to administrator command")));
> +
> + /*
> + * Slot sync worker can be stopped at any time.
> + * Use exit status 1 so the background worker is restarted.
> + */
> + proc_exit(1);
> + }
>
> Explicitly saying "ereport(DEBUG1, errmsg_internal(..." is a bit
> overkill; it is simpler to write this as "elog(DEBUG1, ....);
>
> ======
> src/include/replication/slot.h
>
> 78.
> +/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
> +#define SYNCSLOT_STATE_NONE          'n' /* None for user created slots */
> +#define SYNCSLOT_STATE_INITIATED     'i' /* Sync initiated for the slot but
> + * not completed yet, waiting for
> + * the primary server to catch-up */
> +#define SYNCSLOT_STATE_READY         'r' /* Initialization complete, ready
> + * to be synced further */
>
> Already questioned the same elsewhere. IIUC the same tri-state values
> of other attributes might be used here too without needing to
> introduce 3 new values.
>
> e.g.
>
> #define SYNCSLOT_STATE_DISABLED 'd' /* No syncing for this slot */
> #define SYNCSLOT_STATE_PENDING  'p' /* Sync is enabled but we must
> wait for the primary server to catch up */
> #define SYNCSLOT_STATE_ENABLED  'e' /* Sync is enabled and the slot is
> ready to be synced */
>

responded in comment 11.

> ~~~
>
> 79.
> + /*
> + * Is this a slot created by a sync-slot worker?
> + *
> + * Relevant for logical slots on the physical standby.
> + */
> + char sync_state;
> +
>
> I assumed that "Relevant for" means "Only relevant for". It should say that.
>
> If correct, IMO a better field name might be 'standby_sync_state'
>

 sync_state has some value for primary too. It is not null on primary.
Thus current name seems a better choice.

> ======
> src/test/recovery/t/050_verify_slot_order.pl
>
> 80.
> +$backup_name = 'backup2';
> +$primary->backup($backup_name);
> +
> +# Create standby3
> +my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
> +$standby3->init_from_backup(
> + $primary, $backup_name,
> + has_streaming => 1,
> + has_restoring => 1);
>
> The mixture of 'backup2' for 'standby3' seems confusing. Is there a
> reason to call it backup2?
>
> ~~~
>
> 81.
> +# Verify slot properties on the standby
> +is( $standby3->safe_psql('postgres',
> + q{SELECT failover, sync_state FROM pg_replication_slots WHERE
> slot_name = 'lsub1_slot';}
> +  ),
> +  "t|r",
> +  'logical slot has sync_state as ready and failover as true on standby');
>
> It might be better if the message has the same order as the SQL. Eg.
> "failover as true and sync_state as ready".
>
> ~~~
>
> 82.
> +# Verify slot properties on the primary
> +is( $primary->safe_psql('postgres',
> +    q{SELECT failover, sync_state FROM pg_replication_slots WHERE
> slot_name = 'lsub1_slot';}
> +  ),
> +  "t|n",
> +  'logical slot has sync_state as none and failover as true on primary');
> +
>
> It might be better if the message has the same order as the SQL. Eg.
> "failover as true and sync_state as none".
>
> ~~~
>
> 83.
> +# Test to confirm that restart_lsn of the logical slot on the primary
> is synced to the standby
>
> IMO the major test parts (like this one) may need more highlighting "#
> ---------------------" so those comments don't get lost among all the
> other comments.
>
> ~~~
>
> 84.
> +# let the slots get synced on the standby
> +sleep 2;
>
> Won't this make the test prone to failure on slow machines? Is there
> not a more deterministic way to wait for the sync?
>
> ======
> Kind Regards,
> Peter Smith.
> Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 08:03                                           ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-11 09:30                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-18 12:11                                               ` Zhijie Hou (Fujitsu) <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-12-18 12:11 UTC (permalink / raw)
  To: shveta malik <[email protected]>; Peter Smith <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Amit Kapila <[email protected]>; Nisha Moond <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Monday, December 11, 2023 5:31 PM shveta malik <[email protected]> wrote:
> 
> On Thu, Dec 7, 2023 at 1:33 PM Peter Smith <[email protected]>
> wrote:
> >
> > Hi.
> >
> > Here are my review comments for patch v43-0002.
> >
> 
> > ======
> > src/backend/access/transam/xlogrecovery.c
> >
> > 13.
> > + /*
> > + * Shutdown the slot sync workers to prevent potential conflicts between
> > + * user processes and slotsync workers after a promotion. Additionally,
> > + * drop any slots that have initiated but not yet completed the sync
> > + * process.
> > + */
> > + ShutDownSlotSync();
> > + slotsync_drop_initiated_slots();
> > +
> >
> > Is this where maybe the 'sync_state' should also be updated for
> > everything so you are not left with confusion about different states
> > on a node that is no longer a standby node?
> >
> 
> yes, this is the place. But this needs more thought as it may cause
> too much disk activity during promotion. so let me analyze and come
> back.

Per off-list discussion with Amit.

I think it's fine to keep both READY and NONE on a primary. Because even if we
update the sync_state from READY to NONE on promotion, it doesn't reduce the
complexity for the handling of READY and NONE state. And it's not
straightforward to choose the right place to update sync_state, here is the
analysis:

(related steps on promotion)
1 (patch) shutdown slotsync worker
2 (patch) drop 'i' state slots.
3 remove standby.signal and recovery.signal
4 switch to a new timeline and write the timeline history file 
5 set SharedRecoveryState = RECOVERY_STATE_DONE which means RecoveryInProgress() will return false.

We could not update the sync_state before step 3 because if the update fails after
updating some of slots' state, then the server will be shutdown leaving some
'NONE' state slots. After restarting, the server is still a standby so the slot
sync worker will fail to sync these 'NONE' state slots.

We also could not update it after step 3 and before step 4. Because if any ERROR
when updating, then after restarting the server, although the server will
become a master(as standby.signal is removed), but it can still be made as a
active standby by creating a standby.signal file because the timeline has not
been switched. And in this case, the slot sync worker will also fail to sync
these 'NONE' state slots.

Updating the sync_state after step 4 and before step 5 is OK, but still
It doesn't simplify the handling for both READY and NONE state slots.
Therefore, I think we can retain the READY state slots after promotion as they
can provide information about the slot's origin. I added some comments around
slotsync cleanup codes (in FinishWalRecovery) to mentioned the reason.

Best Regards,
Hou zj



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-07 11:36                                           ` Amit Kapila <[email protected]>
  2023-12-08 12:05                                             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  5 siblings, 1 reply; 113+ messages in thread

From: Amit Kapila @ 2023-12-07 11:36 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Wed, Dec 6, 2023 at 4:53 PM shveta malik <[email protected]> wrote:
>
> v43-001:
> 1) Support of  'failover' dump in pg_dump. It was missing earlier.
>

Review v43-0001
================
1.
+ * However, we do not enable failover for slots created by the table sync
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby.

The reason for not enabling failover for table sync slots is not
clearly mentioned.

2.
During syncing, the local restart_lsn and/or local catalog_xmin of
+ * the newly created slot on the standby are typically ahead of those on the
+ * primary. Therefore, the standby needs to wait for the primary server's
+ * restart_lsn and catalog_xmin to catch up, which takes time.

I think this part of the comment should be moved to 0002 patch. We can
probably describe a bit more about why slot on standby will be ahead
and about waiting time.

3.
validate_standby_slots()
{
...
+ slot = SearchNamedReplicationSlot(name, true);
+
+ if (!slot)
+ goto ret_standby_slot_names_ng;
+
+ if (!SlotIsPhysical(slot))
+ {
+ GUC_check_errdetail("\"%s\" is not a physical replication slot",
+ name);
+ goto ret_standby_slot_names_ng;
+ }

Why the first check (slot not found) doesn't have errdetail? The
goto's in this function look a bit odd, can we try to avoid those?

4.
+ /* Verify syntax and parse string into list of identifiers */
+ if (!SplitIdentifierString(rawname, ',', &elemlist))
+ {
+ /* syntax error in name list */
+ GUC_check_errdetail("List syntax is invalid.");
...
...
+ if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+ {
+ /* This should not happen if GUC checked check_standby_slot_names. */
+ elog(ERROR, "invalid list syntax");

Both are checking the same string but giving different error messages.
I think the error message should be the same in both cases. The first
one seems better.

5. In WalSndFilterStandbySlots(), the comments around else if checks
should move inside the checks. It is hard to read the code in the
current format. I have tried to change the same in the attached.

Apart from the above, I have changed the comments and made some minor
cosmetic changes in the attached. Kindly include in next version if
you are fine with it.

-- 
With Regards,
Amit Kapila.

diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 58e298af89..ef486ca92c 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1674,33 +1674,38 @@ WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 
 			continue;
 		}
-
-		/*
-		 * It may happen that the slot specified in standby_slot_names GUC
-		 * value is dropped, so let's skip over it.
-		 */
 		else if (!slot)
+		{
+			/*
+			 * It may happen that the slot specified in standby_slot_names GUC
+			 * value is dropped, so let's skip over it.
+			 */
 			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
-
-		/*
-		 * If a logical slot name is provided in standby_slot_names, issue a
-		 * WARNING and skip it. Although logical slots are disallowed in the
-		 * GUC check_hook(validate_standby_slots), it is still possible for a
-		 * user to drop an existing physical slot and recreate a logical slot
-		 * with the same name. Since it is harmless, a WARNING should be
-		 * enough, no need to error-out.
-		 */
+		}
 		else if (SlotIsLogical(slot))
+		{
+			/*
+			 * If a logical slot name is provided in standby_slot_names, issue
+			 * a WARNING and skip it. Although logical slots are disallowed in
+			 * the GUC check_hook(validate_standby_slots), it is still possible
+			 * for a user to drop an existing physical slot and recreate a
+			 * logical slot with the same name. Since it is harmless, a WARNING
+			 * should be enough, no need to error-out.
+			 */
 			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
-
-		/*
-		 * Specified physical slot may have been invalidated, so no point in
-		 * waiting for it.
-		 */
+		}
 		else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
+		{
+			/*
+			 * Specified physical slot may have been invalidated, so no point in
+			 * waiting for it.
+			 */
 			warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+		}
 		else
+		{
 			Assert(restart_lsn >= wait_for_lsn);
+		}
 
 		/*
 		 * Reaching here indicates that either the slot has passed the
@@ -1768,9 +1773,9 @@ WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * If the walsender holds a logical slot that has enabled failover, the
- * function also waits for all the specified streaming replication standby
- * servers to confirm receipt of WAL up to RecentFlushPtr.
+ * If the walsender holds a logical slot that has enabled failover, we also
+ * wait for all the specified streaming replication standby servers to
+ * confirm receipt of WAL up to RecentFlushPtr.
  *
  * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
  * detect a shutdown request (either from postmaster or client) we will return
@@ -1790,7 +1795,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 
 	/*
 	 * Check if all the standby servers have confirmed receipt of WAL up to
-	 * RecentFlushPtr if we already know we have enough WAL available.
+	 * RecentFlushPtr even when we already know we have enough WAL available.
 	 *
 	 * Note that we cannot directly return without checking the status of
 	 * standby servers because the standby_slot_names may have changed, which
@@ -1892,7 +1897,10 @@ WalSndWaitForWal(XLogRecPtr loc)
 			wait_for_standby = true;
 		}
 		else
+		{
+			/* already caught up and doesn't need to wait for standby_slots */
 			break;
+		}
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
 		WalSndCaughtUp = true;


Attachments:

  [text/plain] v43_changes_amit_1.patch.txt (3.6K, ../../CAA4eK1LMuNF4svanTTmL4HvQSHrSqaPpHu3g+SQyFUOa=Unqog@mail.gmail.com/2-v43_changes_amit_1.patch.txt)
  download | inline diff:
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 58e298af89..ef486ca92c 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1674,33 +1674,38 @@ WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 
 			continue;
 		}
-
-		/*
-		 * It may happen that the slot specified in standby_slot_names GUC
-		 * value is dropped, so let's skip over it.
-		 */
 		else if (!slot)
+		{
+			/*
+			 * It may happen that the slot specified in standby_slot_names GUC
+			 * value is dropped, so let's skip over it.
+			 */
 			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
-
-		/*
-		 * If a logical slot name is provided in standby_slot_names, issue a
-		 * WARNING and skip it. Although logical slots are disallowed in the
-		 * GUC check_hook(validate_standby_slots), it is still possible for a
-		 * user to drop an existing physical slot and recreate a logical slot
-		 * with the same name. Since it is harmless, a WARNING should be
-		 * enough, no need to error-out.
-		 */
+		}
 		else if (SlotIsLogical(slot))
+		{
+			/*
+			 * If a logical slot name is provided in standby_slot_names, issue
+			 * a WARNING and skip it. Although logical slots are disallowed in
+			 * the GUC check_hook(validate_standby_slots), it is still possible
+			 * for a user to drop an existing physical slot and recreate a
+			 * logical slot with the same name. Since it is harmless, a WARNING
+			 * should be enough, no need to error-out.
+			 */
 			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
-
-		/*
-		 * Specified physical slot may have been invalidated, so no point in
-		 * waiting for it.
-		 */
+		}
 		else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
+		{
+			/*
+			 * Specified physical slot may have been invalidated, so no point in
+			 * waiting for it.
+			 */
 			warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+		}
 		else
+		{
 			Assert(restart_lsn >= wait_for_lsn);
+		}
 
 		/*
 		 * Reaching here indicates that either the slot has passed the
@@ -1768,9 +1773,9 @@ WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * If the walsender holds a logical slot that has enabled failover, the
- * function also waits for all the specified streaming replication standby
- * servers to confirm receipt of WAL up to RecentFlushPtr.
+ * If the walsender holds a logical slot that has enabled failover, we also
+ * wait for all the specified streaming replication standby servers to
+ * confirm receipt of WAL up to RecentFlushPtr.
  *
  * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
  * detect a shutdown request (either from postmaster or client) we will return
@@ -1790,7 +1795,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 
 	/*
 	 * Check if all the standby servers have confirmed receipt of WAL up to
-	 * RecentFlushPtr if we already know we have enough WAL available.
+	 * RecentFlushPtr even when we already know we have enough WAL available.
 	 *
 	 * Note that we cannot directly return without checking the status of
 	 * standby servers because the standby_slot_names may have changed, which
@@ -1892,7 +1897,10 @@ WalSndWaitForWal(XLogRecPtr loc)
 			wait_for_standby = true;
 		}
 		else
+		{
+			/* already caught up and doesn't need to wait for standby_slots */
 			break;
+		}
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
 		WalSndCaughtUp = true;


^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 11:36                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-12-08 12:05                                             ` Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-11 00:16                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-12-08 12:05 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; shveta malik <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Thursday, December 7, 2023 7:37 PM Amit Kapila <[email protected]> wrote:
> 
> On Wed, Dec 6, 2023 at 4:53 PM shveta malik <[email protected]>
> wrote:
> >
> > v43-001:
> > 1) Support of  'failover' dump in pg_dump. It was missing earlier.
> >
> 
> Review v43-0001
> ================
> 1.
> + * However, we do not enable failover for slots created by the table
> + sync
> + * worker. This is because the table sync slot might not be fully
> + synced on the
> + * standby.
> 
> The reason for not enabling failover for table sync slots is not clearly
> mentioned.
> 
> 2.
> During syncing, the local restart_lsn and/or local catalog_xmin of
> + * the newly created slot on the standby are typically ahead of those
> + on the
> + * primary. Therefore, the standby needs to wait for the primary
> + server's
> + * restart_lsn and catalog_xmin to catch up, which takes time.
> 
> I think this part of the comment should be moved to 0002 patch. We can
> probably describe a bit more about why slot on standby will be ahead and
> about waiting time.
> 
> 3.
> validate_standby_slots()
> {
> ...
> + slot = SearchNamedReplicationSlot(name, true);
> +
> + if (!slot)
> + goto ret_standby_slot_names_ng;
> +
> + if (!SlotIsPhysical(slot))
> + {
> + GUC_check_errdetail("\"%s\" is not a physical replication slot",
> + name); goto ret_standby_slot_names_ng; }
> 
> Why the first check (slot not found) doesn't have errdetail? The goto's in this
> function look a bit odd, can we try to avoid those?
> 
> 4.
> + /* Verify syntax and parse string into list of identifiers */ if
> + (!SplitIdentifierString(rawname, ',', &elemlist)) {
> + /* syntax error in name list */
> + GUC_check_errdetail("List syntax is invalid.");
> ...
> ...
> + if (!SplitIdentifierString(standby_slot_names_cpy, ',',
> + &standby_slots)) {
> + /* This should not happen if GUC checked check_standby_slot_names. */
> + elog(ERROR, "invalid list syntax");
> 
> Both are checking the same string but giving different error messages.
> I think the error message should be the same in both cases. The first one seems
> better.
> 
> 5. In WalSndFilterStandbySlots(), the comments around else if checks should
> move inside the checks. It is hard to read the code in the current format. I have
> tried to change the same in the attached.
> 
> Apart from the above, I have changed the comments and made some minor
> cosmetic changes in the attached. Kindly include in next version if you are fine
> with it.

Thanks for the comments and changes, I have addressed them.
Here is the V44 patch set which addressed comments above and [1].

The new version patches also include the follow changes:

V44-0001
* Let the pg_replication_slot_advance also wait for the slots specified
in standby_slot_names to catch up.
* added few test cases to cover the wait/wakeup logic in
walsender related to standby_slot_names.
* ran pgindent.

V44-0002
* added few comments to explain the case when the slot is valid on primary
while is invalidated on standby.

Thanks Ajin for analyzing and making the tests.

The pending comments on 0002 will be addressed in next version.

[1] https://www.postgresql.org/message-id/CAHut%2BPvRD5V-zzTvffDdcnqB1T4JNATKGgw%2BwdQCKAgeCYr0xQ%40mail...

Best Regards,
Hou zj


Attachments:

  [application/octet-stream] v44-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch (7.9K, ../../OS0PR01MB57160AA44EE6E3BBEDB67F84948AA@OS0PR01MB5716.jpnprd01.prod.outlook.com/2-v44-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch)
  download | inline diff:
From 8f7d78ee8931a812e989a6ebfecceb79a9b6d036 Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Wed, 6 Dec 2023 15:58:21 +0530
Subject: [PATCH v44 3/3] Allow slot-sync worker to wait for the cascading
 standbys.

The GUC standby_slot_names is needed to be set on first standby
in order to allow it to wait for confirmation for cascading
standbys before updating logical 'synced' slots in slot-sync worker.
The intent is that the logical slots (synced ones) should not go
ahead of cascading standbys.

For the user created slots on first standby, we already have this wait
logic in place in logical walsender and in pg_logical_slot_get_changes_guts(),
but for synced slots (which can not be consumed yet), we need to make
sure that they are not going ahead of cascading standbys and that is
acheived by introducing the wait in slot-sync worker before we actually
update the slots.
---
 src/backend/replication/logical/slotsync.c | 89 ++++++++++++++++++++--
 src/backend/replication/walsender.c        |  4 +-
 src/include/replication/walsender.h        |  5 ++
 3 files changed, 88 insertions(+), 10 deletions(-)

diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 681bef0d7a..8cba9fb6fa 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -108,7 +108,9 @@ static TimestampTz last_update_time;
  */
 #define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
 
-static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn);
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn,
+									  List **standby_slots);
+
 
 /*
  * Wait for remote slot to pass locally reserved position.
@@ -164,7 +166,7 @@ wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
 		CHECK_FOR_INTERRUPTS();
 
 		/* Handle any termination request if any */
-		ProcessSlotSyncInterrupts(wrconn);
+		ProcessSlotSyncInterrupts(wrconn, NULL /* standby_slots */ );
 
 		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
 
@@ -488,6 +490,52 @@ construct_slot_query(StringInfo s)
 					 " WHERE failover and sync_state != 'i'");
 }
 
+/*
+ * Wait for cascading physical standbys corresponding to physical slots
+ * specified in standby_slot_names GUC to confirm receiving given lsn.
+ */
+static void
+wait_for_standby_confirmation(XLogRecPtr wait_for_lsn,
+							  WalReceiverConn *wrconn)
+{
+	List	   *standby_slots;
+
+	/* Nothing to be done */
+	if (strcmp(standby_slot_names, "") == 0)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	for (;;)
+	{
+		int			rc;
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		/*
+		 * This will reload configuration and will refresh the standby_slots
+		 * as well provided standby_slot_names GUC is changed by the user.
+		 */
+		ProcessSlotSyncInterrupts(wrconn, &standby_slots);
+
+		/*
+		 * XXX: Is waiting for 5 second before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   5000L,
+					   WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+}
+
 /*
  * Synchronize single slot to given position.
  *
@@ -768,6 +816,7 @@ synchronize_slots(WalReceiverConn *wrconn)
 	List	   *remote_slot_list = NIL;
 	MemoryContext oldctx = CurrentMemoryContext;
 	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	XLogRecPtr	max_confirmed_lsn = 0;
 	ListCell   *cell;
 	bool		slot_updated = false;
 	TimestampTz now;
@@ -825,6 +874,9 @@ synchronize_slots(WalReceiverConn *wrconn)
 		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
 		Assert(!isnull);
 
+		if (remote_slot->confirmed_lsn > max_confirmed_lsn)
+			max_confirmed_lsn = remote_slot->confirmed_lsn;
+
 		/*
 		 * It is possible to get null values for LSN and Xmin if slot is
 		 * invalidated on the primary server, so handle accordingly.
@@ -868,6 +920,17 @@ synchronize_slots(WalReceiverConn *wrconn)
 	 */
 	drop_obsolete_slots(remote_slot_list);
 
+	/*
+	 * If there are cascading standbys, wait for their confirmation before we
+	 * update synced logical slots locally.
+	 *
+	 * Instead of waiting on confirmation for lsn of each slot, let us wait
+	 * once for confirmation on max_confirmed_lsn. If that is confirmed by
+	 * each cascading standby, we are good to update all the slots.
+	 */
+	if (remote_slot_list)
+		wait_for_standby_confirmation(max_confirmed_lsn, wrconn);
+
 	/* Now sync the slots locally */
 	foreach(cell, remote_slot_list)
 	{
@@ -1043,7 +1106,7 @@ validate_slotsync_parameters(char **dbname)
  * if validaity fails.
  */
 static void
-slotsync_reread_config(WalReceiverConn *wrconn)
+slotsync_reread_config(WalReceiverConn *wrconn, List **standby_slots)
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 	char	   *slotname = pstrdup(PrimarySlotName);
@@ -1057,8 +1120,14 @@ slotsync_reread_config(WalReceiverConn *wrconn)
 	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
 	Assert(dbname);
 
-	ConfigReloadPending = false;
-	ProcessConfigFile(PGC_SIGHUP);
+	/*
+	 * Reload configs and recreate the standby_slot_names_list if GUC
+	 * standby_slot_names changed.
+	 */
+	if (standby_slots)
+		WalSndRereadConfigAndReInitSlotList(standby_slots);
+	else
+		ProcessConfigFile(PGC_SIGHUP);
 
 	conninfoChanged = strcmp(conninfo, PrimaryConnInfo) != 0;
 	slotnameChanged = strcmp(slotname, PrimarySlotName) != 0;
@@ -1101,7 +1170,8 @@ slotsync_reread_config(WalReceiverConn *wrconn)
  * Interrupt handler for main loop of slot sync worker.
  */
 static void
-ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn,
+						  List **standby_slots)
 {
 	CHECK_FOR_INTERRUPTS();
 
@@ -1117,7 +1187,10 @@ ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
 
 
 	if (ConfigReloadPending)
-		slotsync_reread_config(wrconn);
+	{
+		ConfigReloadPending = false;
+		slotsync_reread_config(wrconn, standby_slots);
+	}
 }
 
 /*
@@ -1191,7 +1264,7 @@ ReplSlotSyncWorkerMain(Datum main_arg)
 		int			rc;
 		long		naptime;
 
-		ProcessSlotSyncInterrupts(wrconn);
+		ProcessSlotSyncInterrupts(wrconn, NULL /* standby_slots */ );
 
 		naptime = synchronize_slots(wrconn);
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 6b65c3c7dc..5ea47430a7 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1609,7 +1609,7 @@ PhysicalWakeupLogicalWalSnd(void)
  * Reload the config file and reinitialize the standby slot list if the GUC
  * standby_slot_names has changed.
  */
-static void
+void
 WalSndRereadConfigAndReInitSlotList(List **standby_slots)
 {
 	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
@@ -1634,7 +1634,7 @@ WalSndRereadConfigAndReInitSlotList(List **standby_slots)
  * it removes slots that have been invalidated, dropped, or converted to
  * logical slots.
  */
-static void
+void
 WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 {
 	ListCell   *lc;
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index ecd212c7b6..07b2c719ae 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -13,6 +13,7 @@
 #define _WALSENDER_H
 
 #include "access/xlogdefs.h"
+#include "nodes/pg_list.h"
 
 /*
  * What to do with a snapshot in create replication slot command.
@@ -48,7 +49,11 @@ extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn,
+									 List **standby_slots);
 extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
+extern List *WalSndGetStandbySlots(void);
+extern void WalSndRereadConfigAndReInitSlotList(List **standby_slots);
 
 /*
  * Remember that we want to wakeup walsenders later
-- 
2.30.0.windows.2



  [application/octet-stream] v44-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (145.9K, ../../OS0PR01MB57160AA44EE6E3BBEDB67F84948AA@OS0PR01MB5716.jpnprd01.prod.outlook.com/3-v44-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From d07bf40619dadf3e40d75331fbdd106b02a3efce Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Wed, 6 Dec 2023 15:52:16 +0530
Subject: [PATCH v44 1/3] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to
the physical standbys so that logical replication can be resumed after
failover. It is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API.

Ex1:
CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

Ex2: (failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

A new replication command called ALTER_REPLICATION_SLOT and a
corresponding walreceiver API function named walrcv_alter_slot have been
implemented. They allow subscribers or users to modify the failover
property of a replication slot on the publisher.

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).
---
 contrib/test_decoding/expected/slot.out       |  58 +++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |  20 +-
 doc/src/sgml/ref/create_subscription.sgml     |  23 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       | 110 +++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  38 +-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  53 ++-
 src/backend/replication/logical/worker.c      |  67 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 182 ++++++++-
 src/backend/replication/slotfuncs.c           |  25 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 344 +++++++++++++++++-
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_dump/pg_dump.c                     |  20 +-
 src/bin/pg_dump/pg_dump.h                     |   1 +
 src/bin/pg_upgrade/info.c                     |   5 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 .../t/050_standby_failover_slots_sync.pl      | 298 +++++++++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 +++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 50 files changed, 1540 insertions(+), 170 deletions(-)
 create mode 100644 src/test/recovery/t/050_standby_failover_slots_sync.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 94d1eb2b81..30d9b53e03 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4360,6 +4360,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..481e397bad 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
@@ -230,6 +233,17 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
+
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property of the new slot may differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription. When creating the slot,
+      ensure the slot failover property matches the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter value of the subscription.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..fa6cd1c43f 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,29 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..67826b89c2 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,34 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If the slot_name is specified without the create_slot option,
+			 * it is possible that the user intends to use an existing slot on
+			 * the publisher, so here we alter the failover property of the
+			 * slot to match the failover value in subscription.
+			 *
+			 * We do not need to change the failover to false if the server
+			 * does not support failover (e.g. pre-PG17)
+			 */
+			else if (opts.slot_name &&
+					 (failover_enabled || walrcv_server_version(wrconn) >= 170000))
+			{
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+								opts.slot_name,
+								failover_enabled ? "true" : "false")));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,13 +1331,22 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
@@ -1334,13 +1395,26 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 						/* translator: %s is an SQL ALTER command */
 								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
 										 isadd ?
@@ -1389,7 +1463,19 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 					ereport(ERROR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "two_phase"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "failover"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 4152d1ddbc..ff65fdb4d9 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -888,8 +892,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -918,7 +922,8 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+			appendStringInfoString(&cmd, "FAILOVER, ");
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -987,6 +992,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index df3c42eb5d..d2d8bf1a7a 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -614,15 +614,28 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "two_phase")));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "failover")));
+
 				should_exit = true;
 			}
 		}
@@ -1420,7 +1433,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1722,10 +1736,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1733,9 +1749,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1749,9 +1764,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..e46a1955e8 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,6 +132,33 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * failover = true when creating the subscription. Enabling failover allows us
+ * to smoothly transition to the promoted standby, ensuring that we can
+ * subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
@@ -3947,6 +3974,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4510,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4568,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4608,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..ca8b07a095 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -248,10 +260,13 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     during getting changes, if the two_phase option is enabled it can skip
  *     prepare because by that time start decoding point has been moved. So the
  *     user will only get commit prepared.
+ * failover: Allows the slot to be synced to physical standbys so that logical
+ *     replication can be resumed after failover.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +695,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2200,138 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+	bool		ok = true;
+
+	/* Need a modifiable copy of string */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into list of identifiers */
+	if (!(ok = SplitIdentifierString(rawname, ',', &elemlist)))
+		GUC_check_errdetail("List syntax is invalid.");
+
+	/*
+	 * If there is a syntax error in the name or if the replication slots'
+	 * data is not initialized yet (i.e., we are in the startup process), skip
+	 * the slot verification.
+	 */
+	if (!ok || !ReplicationSlotCtl)
+	{
+		pfree(rawname);
+		list_free(elemlist);
+		return ok;
+	}
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			GUC_check_errdetail("replication slot \"%s\" does not exist",
+								name);
+			ok = false;
+			break;
+		}
+
+		if (!SlotIsPhysical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			ok = false;
+			break;
+		}
+	}
+
+	pfree(rawname);
+	list_free(elemlist);
+	return ok;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "list syntax is invalid");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..c87f61666d 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -491,6 +501,12 @@ pg_logical_replication_slot_advance(XLogRecPtr moveto)
 											   .segment_close = wal_segment_close),
 									NULL, NULL, NULL);
 
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to moveto lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(moveto);
+
 		/*
 		 * Start reading at the slot's restart_lsn, which we know to point to
 		 * a valid record.
@@ -679,6 +695,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +751,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +791,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 26ded928a7..ca61a99785 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -387,7 +387,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..b5493fcd69 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool		failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,243 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up the logical walsender processes with failover-enabled slots if the
+ * physical slot of the current walsender is specified in standby_slot_names
+ * GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn. Additionally,
+ * it removes slots that have been invalidated, dropped, or converted to
+ * logical slots.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
+		bool		invalidated = false;
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (slot && SlotIsPhysical(slot))
+		{
+			SpinLockAcquire(&slot->mutex);
+			restart_lsn = slot->data.restart_lsn;
+			invalidated = slot->data.invalidated != RS_INVAL_NONE;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Continue if the current slot hasn't caught up. */
+		if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
+			restart_lsn < wait_for_lsn)
+		{
+			/* Log warning if no active_pid for this physical slot */
+			if (slot->active_pid == 0)
+				ereport(WARNING,
+						errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+							   name, "standby_slot_names"),
+						errdetail("Logical replication is waiting on the "
+								  "standby associated with \"%s\"", name),
+						errhint("Consider starting standby associated with "
+								"\"%s\" or amend standby_slot_names", name));
+
+			continue;
+		}
+		else if (!slot)
+		{
+			/*
+			 * It may happen that the slot specified in standby_slot_names GUC
+			 * value is dropped, so let's skip over it.
+			 */
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+		}
+		else if (SlotIsLogical(slot))
+		{
+			/*
+			 * If a logical slot name is provided in standby_slot_names, issue
+			 * a WARNING and skip it. Although logical slots are disallowed in
+			 * the GUC check_hook(validate_standby_slots), it is still
+			 * possible for a user to drop an existing physical slot and
+			 * recreate a logical slot with the same name. Since it is
+			 * harmless, a WARNING should be enough, no need to error-out.
+			 */
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+		}
+		else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
+		{
+			/*
+			 * Specified physical slot may have been invalidated, so no point
+			 * in waiting for it.
+			 */
+			warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+		}
+		else
+		{
+			Assert(restart_lsn >= wait_for_lsn);
+		}
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving the given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, we also
+ * wait for all the specified streaming replication standby servers to
+ * confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr even when we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
-	if (RecentFlushPtr != InvalidXLogRecPtr &&
-		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	if (!XLogRecPtrIsInvalid(RecentFlushPtr) && loc <= RecentFlushPtr)
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know
+		 * we have enough WAL available and all the standby servers have
+		 * confirmed receipt of WAL up to RecentFlushPtr. This is particularly
+		 * interesting if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1835,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1850,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,9 +1889,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
+		{
+			/* already caught up and doesn't need to wait for standby_slots */
 			break;
+		}
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
 		WalSndCaughtUp = true;
@@ -1654,9 +1940,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2107,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2344,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3607,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3678,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another
+	 * CV that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 6474e35ec0..4b776266a4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4572,6 +4572,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..5d940b72cd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -329,6 +329,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8c0b5486b9..373c2514df 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4618,6 +4618,7 @@ getSubscriptions(Archive *fout)
 	int			i_subsynccommit;
 	int			i_subpublications;
 	int			i_suborigin;
+	int			i_subfailoverstate;
 	int			i,
 				ntups;
 
@@ -4673,14 +4674,22 @@ getSubscriptions(Archive *fout)
 		appendPQExpBufferStr(query,
 							 " s.subpasswordrequired,\n"
 							 " s.subrunasowner,\n"
-							 " s.suborigin\n");
+							 " s.suborigin,\n");
 	else
 		appendPQExpBuffer(query,
 						  " 't' AS subpasswordrequired,\n"
 						  " 't' AS subrunasowner,\n"
-						  " '%s' AS suborigin\n",
+						  " '%s' AS suborigin,\n",
 						  LOGICALREP_ORIGIN_ANY);
 
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(query,
+							 " s.subfailoverstate\n");
+	else
+		appendPQExpBuffer(query,
+						  " '%c' AS subfailoverstate\n",
+						  LOGICALREP_FAILOVER_STATE_DISABLED);
+
 	appendPQExpBufferStr(query,
 						 "FROM pg_subscription s\n"
 						 "WHERE s.subdbid = (SELECT oid FROM pg_database\n"
@@ -4709,6 +4718,7 @@ getSubscriptions(Archive *fout)
 	i_subsynccommit = PQfnumber(res, "subsynccommit");
 	i_subpublications = PQfnumber(res, "subpublications");
 	i_suborigin = PQfnumber(res, "suborigin");
+	i_subfailoverstate = PQfnumber(res, "subfailoverstate");
 
 	subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo));
 
@@ -4746,6 +4756,8 @@ getSubscriptions(Archive *fout)
 		subinfo[i].subpublications =
 			pg_strdup(PQgetvalue(res, i, i_subpublications));
 		subinfo[i].suborigin = pg_strdup(PQgetvalue(res, i, i_suborigin));
+		subinfo[i].subfailoverstate =
+			pg_strdup(PQgetvalue(res, i, i_subfailoverstate));
 
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(subinfo[i].dobj), fout);
@@ -4771,6 +4783,7 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	int			npubnames = 0;
 	int			i;
 	char		two_phase_disabled[] = {LOGICALREP_TWOPHASE_STATE_DISABLED, '\0'};
+	char		failover_disabled[] = {LOGICALREP_FAILOVER_STATE_DISABLED, '\0'};
 
 	/* Do nothing in data-only dump */
 	if (dopt->dataOnly)
@@ -4818,6 +4831,9 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	if (strcmp(subinfo->subtwophasestate, two_phase_disabled) != 0)
 		appendPQExpBufferStr(query, ", two_phase = on");
 
+	if (strcmp(subinfo->subfailoverstate, failover_disabled) != 0)
+		appendPQExpBufferStr(query, ", failover = true");
+
 	if (strcmp(subinfo->subdisableonerr, "t") == 0)
 		appendPQExpBufferStr(query, ", disable_on_error = true");
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2fe3cbed9a..f62a4dfd4b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -671,6 +671,7 @@ typedef struct _SubscriptionInfo
 	char	   *subsynccommit;
 	char	   *subpublications;
 	char	   *suborigin;
+	char	   *subfailoverstate;
 } SubscriptionInfo;
 
 /*
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..e16286f18c 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,6 +679,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
@@ -687,6 +688,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +699,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..2d8bcb26f9 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced to the
+								 * physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 020e7aa1cc..cb3a2b3aed 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -159,7 +159,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -179,8 +179,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 049801186c..905964a2e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3327,7 +3327,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb58dee3bc..d906734750 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..5ddad69348 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)? Only
+	 * relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 949e874f21..f1135762fb 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -355,9 +355,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									  const char *slotname,
+									  bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -399,6 +410,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -428,8 +440,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 60313980a9..ecd212c7b6 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -12,6 +12,8 @@
 #ifndef _WALSENDER_H
 #define _WALSENDER_H
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -45,6 +47,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index db73408937..84bb79ac0f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -256,7 +256,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..3be3ee52fc 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_verify_slot_order.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
new file mode 100644
index 0000000000..09b5d006a5
--- /dev/null
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -0,0 +1,298 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+##################################################
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+##################################################
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres',
+	"CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+$subscriber1->safe_psql('postgres',
+	"CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true
+$subscriber1->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);"
+);
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql('postgres',
+	"CREATE TABLE tab_int (a int PRIMARY KEY);");
+$subscriber2->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub2 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);");
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby comes
+# up.
+$standby1->stop;
+
+# Create some data on primary
+my $primary_row_count   = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('regress_mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary until standby1 acknowledges changes"
+);
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 acknowledges changes");
+
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby slot's
+# restart_lsn is advanced or the slots is removed from the standby_slot_names
+# list
+$publisher->safe_psql('postgres', "TRUNCATE tab_int;");
+$publisher->wait_for_catchup('regress_mysub1');
+$standby1->stop;
+
+##################################################
+# Verify that when using pg_logical_slot_get_changes to consume changes from a
+# logical slot with failover enabled, it will also wait for the slots specified
+# in standby_slot_names to catch up.
+##################################################
+
+# Create a logical 'test_decoding' replication slot with failover enabled
+$publisher->safe_psql('postgres',
+	"SELECT pg_create_logical_replication_slot('test_slot', 'test_decoding', false, false, true);"
+);
+
+my $back_q = $primary->background_psql('postgres', on_error_stop => 0);
+my $pid = $back_q->query('SELECT pg_backend_pid()');
+
+# Try and get changes from the logical slot with failover enabled.
+my $offset = -s $primary->logfile;
+$back_q->query_until(qr//,
+	"SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL);\n");
+
+# Wait until the primary server logs a warning indicating that it is waiting
+# for the sb1_slot to catch up.
+$primary->wait_for_log(
+	qr/WARNING: ( [A-Z0-9]+:)? replication slot \"sb1_slot\" specified in parameter \"standby_slot_names\" does not have active_pid/,
+	$offset);
+
+ok($primary->safe_psql('postgres', "SELECT pg_cancel_backend($pid)"),
+	"cancelling pg_logical_slot_get_changes command");
+
+$publisher->safe_psql('postgres',
+	"SELECT pg_drop_replication_slot('test_slot');"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we manually advance this slot's LSN to the
+# latest position.
+##################################################
+
+# Create some data on primary
+$primary_row_count = 10;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# The subscription that's up and running and is enabled for failover doesn't
+# get the data from primary and keeps waiting for the standby specified in
+# standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary as long as standby1 restart_lsn has not been updated"
+);
+
+# Advance the lsn of the standby slot manually
+$primary->safe_psql('postgres',
+	"SELECT * FROM pg_replication_slot_advance('sb1_slot', pg_current_wal_lsn());"
+);
+
+# Now that the standby lsn has advanced, primary must send the decoded
+# changes to the subscription
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1's restart_lsn has been updated"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we remove the slot from standby_slot_names.
+##################################################
+
+# Create some data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(11,20);");
+
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 10 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data as the sb1_slot doesn't catch up");
+
+# Remove the standby from the standby_slot_names list and reload the
+# configuration
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', "''");
+$primary->psql('postgres', "SELECT pg_reload_conf()");
+
+# Now that the standby lsn has advanced, primary must send the decoded
+# changes to the subscription.
+$publisher->wait_for_catchup('regress_mysub1');
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 20 FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 is removed from the standby_slot_names list"
+);
+
+# Put the standby back on the primary_slot_name for the rest of the tests
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', 'sb1_slot');
+$primary->restart();
+
+##################################################
+# Test that when a subscription with failover enabled is created, it will alter
+# the failover property of the corresponding slot on the publisher.
+##################################################
+
+# Create a slot on the publisher with failover disabled
+$primary->safe_psql('postgres',
+	"SELECT 'init' FROM pg_create_logical_replication_slot('lsub3_slot', 'pgoutput', false, false, false);"
+);
+
+# Confirm that the failover flag on the slot is turned off
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"f",
+	'logical slot has failover false on primary');
+
+# Create another subscription enabling failover
+$subscriber1->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub3 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub3_slot, copy_data=false, failover = true, create_slot = false);"
+);
+
+# Confirm that the failover flag on the slot has now been turned on
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"t",
+	'logical slot has failover true on primary');
+
+$subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 38a86575e1..175bcecfd1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3865,6 +3866,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.30.0.windows.2



  [application/octet-stream] v44-0002-Add-logical-slot-sync-capability-to-the-physical.patch (84.6K, ../../OS0PR01MB57160AA44EE6E3BBEDB67F84948AA@OS0PR01MB5716.jpnprd01.prod.outlook.com/4-v44-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From f06aade436ce58758d933c08d613404b7e3b1283 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Fri, 8 Dec 2023 17:37:13 +0800
Subject: [PATCH v44 2/3] Add logical slot sync capability to the physical standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover. All the failover logical
replication slots on the primary (assuming configurations are
appropriate) are automatically created on the physical standbys and
are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The nap time of worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. And if
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated.

If a logical slot on the primary is valid but is invalidated on the standby,
then that slot is dropped and recreated on the standby in next sync-cycle. It
is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently). This situation may occur due to the
following reasons:
- The max_slot_wal_keep_size on the standby is insufficient to retain WAL
  records from the restart_lsn of the slot.
- primary_slot_name is temporarily reset to null and the physical slot is
  removed.
- The primary changes wal_level to a level lower than logical.

Slots synced on the standby can be identified using 'sync_state' column of
pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but waiting for the remote slot on the
     primary server to catch up.
'r': ready for periodic syncs.
---
 doc/src/sgml/bgworker.sgml                    |   16 +-
 doc/src/sgml/config.sgml                      |   35 +-
 doc/src/sgml/logicaldecoding.sgml             |   34 +
 doc/src/sgml/system-views.sgml                |   25 +
 src/backend/access/transam/xlogrecovery.c     |   10 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    4 +
 src/backend/postmaster/postmaster.c           |    6 +
 .../libpqwalreceiver/libpqwalreceiver.c       |   42 +
 src/backend/replication/logical/Makefile      |    1 +
 src/backend/replication/logical/logical.c     |   20 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1329 +++++++++++++++++
 src/backend/replication/logical/tablesync.c   |    1 +
 src/backend/replication/logical/worker.c      |   15 +-
 src/backend/replication/slot.c                |   20 +-
 src/backend/replication/slotfuncs.c           |   37 +-
 src/backend/replication/walsender.c           |    6 +-
 src/backend/storage/ipc/ipci.c                |    2 +
 src/backend/tcop/postgres.c                   |   12 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   10 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/commands/subscriptioncmds.h       |    4 +
 src/include/postmaster/bgworker.h             |    1 +
 src/include/replication/slot.h                |   22 +-
 src/include/replication/walreceiver.h         |   19 +
 src/include/replication/worker_internal.h     |   12 +
 .../t/050_standby_failover_slots_sync.pl      |  127 ++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    2 +
 33 files changed, 1808 insertions(+), 29 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2c393385a9..377f301b63 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -121,11 +121,17 @@ typedef struct BackgroundWorker
    <literal>BgWorkerStart_ConsistentState</literal> (start as soon as a consistent state
    has been reached in a hot standby, allowing processes to connect to
    databases and run read-only queries), and
-   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system has
-   entered normal read-write state).  Note the last two values are equivalent
-   in a server that's not a hot standby.  Note that this setting only indicates
-   when the processes are to be started; they do not stop when a different state
-   is reached.
+   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system
+   has entered normal read-write state. Note that the
+   <literal>BgWorkerStart_ConsistentState</literal> and
+   <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
+   in a server that's not a hot standby), and
+   <literal>BgWorkerStart_ConsistentState_HotStandby</literal> (same meaning as
+   <literal>BgWorkerStart_ConsistentState</literal> but it is more strict in
+   terms of the server i.e. start the  worker only if it is hot-standby; if it is
+   consistent state in non-standby, worker will not be started). Note that this
+   setting only indicates when the processes are to be started; they do not stop
+   when a different state is reached.
   </para>
 
   <para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 30d9b53e03..1977962caf 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4373,6 +4373,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must enable
+        <varname>enable_syncslot</varname> for the standbys to receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4566,10 +4572,15 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           A password needs to be provided too, if the sender demands password
           authentication.  It can be provided in the
           <varname>primary_conninfo</varname> string, or in a separate
-          <filename>~/.pgpass</filename> file on the standby server (use
+          <filename>~/.pgpass</filename> file on the standby server. (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          Specify <literal>dbname</literal> in
+          <varname>primary_conninfo</varname> string to allow synchronization
+          of slots from the primary server to the standby server.
+          This will only be used for slot synchronization. It is ignored
+          for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4894,6 +4905,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..0defa0ec2b 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,40 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and set
+     <varname>enable_syncslot</varname> on the standby. For the synchronization
+     to work, it is mandatory to have physical replication slot between the
+     primary and the standby. This physical replication slot for the standby
+     should be listed in <varname>standby_slot_names</varname> on the primary
+     to prevent the subscriber from consuming changes faster than the hot
+     standby. Additionally, similar to creating a logical replication slot
+     on the hot standby, <varname>hot_standby_feedback</varname> should be
+     set on the standby and a physical slot between the primary and the standby
+     should be used.
+    </para>
+
+    <para>
+     By enabling synchronization of slots, logical replication can be resumed
+     after failover depending upon the
+     <link linkend="view-pg-replication-slots">pg_replication_slots</link>.<structfield>sync_state</structfield>
+     for the synchronized slots on the standby at the time of failover.
+     The slots which were in ready sync_state ('r') on the standby before
+     failover can be used for logical replication after failover. However,
+     the slots which were in initiated sync_state ('i) and were not
+     sync-ready ('r') at the time of failover will be dropped and logical
+     replication for such slots can not be resumed after failover. This applies
+     to the case where a logical subscription is disabled before failover and is
+     enabled after failover. If the synchronized slot due to disabled
+     subscription could not be made sync-ready ('r') on standby, then the
+     subscription can not be resumed after failover even when enabled.
+     If the primary is idle, making the synchronized slot on the standby
+     as sync-ready ('r') for enabled subscription may take noticeable time.
+     This can be sped up by calling the
+     <function>pg_log_standby_snapshot</function> function on the primary.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..3f6e2f82c8 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,31 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has enabled slots synchronization.
+      </para>
+      <para>
+       State code:
+       <literal>n</literal> = none for user created slots,
+       <literal>i</literal> = sync initiated for the slot but slot is not ready
+        yet for periodic syncs,
+       <literal>r</literal> = ready for periodic syncs.
+      </para>
+      <para>
+      The hot standby can have any of these sync_state for the slots but on a
+      hot standby, the slots with state 'r' and 'i' can neither be used for logical
+      decoded nor dropped by the user. The primary server will have sync_state
+      as 'n' for all the slots. But if the standby is promoted to become the
+      new primary server, sync_state can be seen 'r' as well. On this new
+      primary server, slots with sync_state as 'r' and 'n' will behave the same.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c61566666a..9fb09ba0fc 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1436,15 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index c345639086..854c967910 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -22,6 +22,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "replication/worker_internal.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -130,6 +131,9 @@ static const struct
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index ae31d66930..d75bb378b7 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -117,6 +117,7 @@
 #include "postmaster/syslogger.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -1006,6 +1007,8 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	SlotSyncWorkerRegister();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
@@ -5743,6 +5746,9 @@ bgworker_should_start_now(BgWorkerStartTime start_time)
 		case PM_HOT_STANDBY:
 			if (start_time == BgWorkerStart_ConsistentState)
 				return true;
+			else if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
+					 pmState != PM_RUN)
+				return true;
 			/* fall through */
 
 		case PM_RECOVERY:
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index ff65fdb4d9..7e1492f046 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -418,6 +421,45 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	PQconninfoOption *opt;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..7de68a61e2 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,26 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	/*
+	 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+	 * promotion.
+	 */
+	if (!RecoveryInProgress() && slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+		elog(ERROR, "replication slot \"%s\" was not synced completely from the primary server",
+			 NameStr(slot->data.name));
+
+	/*
+	 * Do not allow consumption of a "synchronized" slot until the standby
+	 * gets promoted.
+	 */
+	if (RecoveryInProgress() && slot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot use replication slot \"%s\" for logical decoding",
+						NameStr(slot->data.name)),
+				 errdetail("This slot is being synced from the primary server."),
+				 errhint("Specify another replication slot.")));
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..681bef0d7a
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1329 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * It also takes care of dropping the slots which were created by it and are
+ * currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/table.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_database.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/*
+ * Struct for sharing information between startup process and slot
+ * sync worker.
+ *
+ * Slot sync worker's pid is needed by startup process in order to
+ * shut it down during promotion.
+ */
+typedef struct SlotSyncWorkerCtx
+{
+	pid_t		pid;
+	slock_t		mutex;
+} SlotSyncWorkerCtx;
+
+SlotSyncWorkerCtx *SlotSyncWorker = NULL;
+
+/* GUC variable */
+bool		enable_syncslot = false;
+
+/* The last sync-cycle time when the worker updated any of the slots. */
+static TimestampTz last_update_time;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+/*
+ * Number of attempts for wait_for_primary_slot_catchup() after
+ * which it aborts the wait and the slot sync worker then moves
+ * to the next slot creation/sync.
+ */
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
+
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn);
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * *persist will be set to false if the slot has disappeared or was invalidated
+ * on the primary; otherwise, it will be set to true.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *persist)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+	if (persist)
+		*persist = true;
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn, confirmed_flush_lsn,"
+					 " catalog_xmin FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		XLogRecPtr	new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *slot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Handle any termination request if any */
+		ProcessSlotSyncInterrupts(wrconn);
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot info for slot \"%s\" from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" disappeared from the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(slot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" invalidated on the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			ExecClearTuple(slot);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * Once we got valid restart_lsn, then confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(slot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\"",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up.
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			slots = lappend(slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(slots);
+}
+
+/*
+ * Get list of local logical slot names which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slot_names(void)
+{
+	List	   *localSyncedSlots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			localSyncedSlots = lappend(localSyncedSlots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return localSyncedSlots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+check_sync_slot_validity(ReplicationSlot *local_slot, List *remote_slots,
+						 bool *locally_invalidated)
+{
+	ListCell   *cell;
+
+	foreach(cell, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			SpinLockAcquire(&local_slot->mutex);
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+			SpinLockRelease(&local_slot->mutex);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Additionally, it drops slots that are valid on the primary but got
+ * invalidated on the standby. This situation may occur due to the following
+ * reasons:
+ * - The max_slot_wal_keep_size on the standby is insufficient to retain WAL
+ *   records from the restart_lsn of the slot.
+ * - primary_slot_name is temporarily reset to null and the physical slot is
+ *   removed.
+ * - The primary changes wal_level to a level lower than logical.
+ *
+ * The assumption is that these dropped local invalidated slots will get
+ * recreated in next sync-cycle and it is okay to drop and recreate such slots
+ * as long as these are not consumable on the standby (which is the case
+ * currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slot_list = NIL;
+	ListCell   *lc_slot;
+
+	/*
+	 * Get the list of local 'synced' slot so that those not on remote could
+	 * be dropped.
+	 */
+	local_slot_list = get_local_synced_slot_names();
+
+	foreach(lc_slot, local_slot_list)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc_slot);
+		bool		local_exists = false;
+		bool		locally_invalidated = false;
+
+		local_exists = check_sync_slot_validity(local_slot, remote_slot_list,
+												&locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!local_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Constructs the query in order to get failover logical slots
+ * information from the primary server.
+ */
+static void
+construct_slot_query(StringInfo s)
+{
+	/*
+	 * Fetch slots with failover enabled.
+	 *
+	 * If we are on cascading standby, we should fetch only those slots from
+	 * the first standby which have sync_state as either 'n' or 'r'. Slots
+	 * with sync_state as 'i' are not sync ready yet. And when we are on the
+	 * first standby, the primary server is supposed to have slots with
+	 * sync_state as 'n' only (or it may have all 'n', 'r' and 'i' if standby
+	 * is promoted as primary). Thus in all the cases, filter sync_state !='i'
+	 * is appropriate one.
+	 */
+	appendStringInfo(s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover and sync_state != 'i'");
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ */
+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+					 bool *slot_updated)
+{
+	ReplicationSlot *s;
+	char		sync_state = 0;
+
+	/*
+	 * Make sure that concerned WAL is received before syncing slot to target
+	 * lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* Search for the named slot */
+	if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		SpinLockAcquire(&s->mutex);
+		sync_state = s->data.sync_state;
+		SpinLockRelease(&s->mutex);
+	}
+
+	StartTransactionCommand();
+
+	/*
+	 * Already existing slot (created by slot sync worker) and ready for sync,
+	 * acquire and sync it.
+	 */
+	if (sync_state == SYNCSLOT_STATE_READY)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&MyReplicationSlot->mutex);
+			MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&MyReplicationSlot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * With hot_standby_feedback enabled and invalidations handled
+		 * apropriately as above, this should never happen.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn)
+		{
+			ereport(ERROR,
+					errmsg("not synchronizing local slot \"%s\" LSN(%X/%X)"
+						   " to remote slot's LSN(%X/%X) as synchronization "
+						   " would move it backwards", remote_slot->name,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   LSN_FORMAT_ARGS(remote_slot->restart_lsn)));
+
+			goto cleanup;
+		}
+
+		if (remote_slot->confirmed_lsn != MyReplicationSlot->data.confirmed_flush ||
+			remote_slot->restart_lsn != MyReplicationSlot->data.restart_lsn ||
+			remote_slot->catalog_xmin != MyReplicationSlot->data.catalog_xmin)
+		{
+			/* Update LSN of slot to remote slot's current position */
+			local_slot_update(remote_slot);
+			ReplicationSlotSave();
+			*slot_updated = true;
+		}
+	}
+
+	/*
+	 * Already existing slot but not ready (i.e. waiting for the primary
+	 * server to catch-up), lets attempt to make it sync-ready now.
+	 */
+	else if (sync_state == SYNCSLOT_STATE_INITIATED)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&MyReplicationSlot->mutex);
+			MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&MyReplicationSlot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * Refer the slot creation part (last 'else' block) for more details
+		 * on this wait.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+			{
+				goto cleanup;
+			}
+		}
+
+		/*
+		 * Wait for primary is over, update the lsns and mark the slot as
+		 * READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Save the changes */
+		ReplicationSlotMarkDirty();
+		ReplicationSlotSave();
+
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready "
+							"now", remote_slot->name));
+	}
+	/* User created slot with the same name exists, raise ERROR. */
+	else if (sync_state == SYNCSLOT_STATE_NONE)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("skipping sync of slot \"%s\" as it is a user created"
+						" slot", remote_slot->name),
+				 errdetail("This slot has failover enabled on the primary and"
+						   " thus is sync candidate but user created slot with"
+						   " the same name already exists on the standby")));
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+		ReplicationSlot *slot;
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover,
+							  SYNCSLOT_STATE_INITIATED);
+
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * If the local restart_lsn and/or local catalog_xmin is ahead of
+		 * those on the remote then we cannot create the local slot in sync
+		 * with the primary server because that would mean moving the local
+		 * slot backwards and we might not have WALs retained for old LSN. In
+		 * this case we will wait for the primary server's restart_lsn and
+		 * catalog_xmin to catch up with the local one before attempting the
+		 * sync.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			bool		persist;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &persist))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid) and attempt the wait and synchronization in
+				 * the next cycle.
+				 */
+				if (persist)
+				{
+					ReplicationSlotPersist();
+					*slot_updated = true;
+				}
+
+				goto cleanup;
+			}
+		}
+
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready "
+							"now", remote_slot->name));
+	}
+
+cleanup:
+
+	ReplicationSlotRelease();
+	CommitTransactionCommand();
+
+	return;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and update
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns nap time for the next sync-cycle.
+ */
+static long
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *slot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	MemoryContext oldctx = CurrentMemoryContext;
+	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	ListCell   *cell;
+	bool		slot_updated = false;
+	TimestampTz now;
+
+	/* The primary_slot_name is not set yet or WALs not received yet */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		return naptime;
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* The syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	/*
+	 * Make result tuples live outside TopTransactionContext to make them
+	 * accessible even after transaction is committed.
+	 */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct query to get slots info from the primary server */
+	initStringInfo(&s);
+	construct_slot_query(&s);
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(slot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(slot, 5,
+																	   &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(slot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(slot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(slot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(slot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(slot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(cell, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		synchronize_one_slot(wrconn, remote_slot, &slot_updated);
+	}
+
+	now = GetCurrentTimestamp();
+
+	/*
+	 * If any of the slots get updated in this sync-cycle, retain default
+	 * naptime and update 'last_update_time' in slot sync worker. But if no
+	 * activity is observed in this sync-cycle, then increase naptime provided
+	 * inactivity time reaches threshold.
+	 */
+	if (slot_updated)
+		last_update_time = now;
+
+	else if (TimestampDifferenceExceeds(last_update_time,
+										now, WORKER_INACTIVITY_THRESHOLD_MS))
+		naptime = WORKER_INACTIVITY_NAPTIME_MS;
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	return naptime;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker",
+							&err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Connects primary to validate the slot specified in primary_slot_name.
+ *
+ * Exits the worker if physical slot with the specified name does not exist.
+ */
+static void
+validate_primary_slot(WalReceiverConn *wrconn)
+{
+	WalRcvExecResult *res;
+	Oid			slotRow[1] = {BOOLOID};
+	StringInfoData cmd;
+	bool		isnull;
+	TupleTableSlot *slot;
+	bool		valid;
+	bool		tuple_ok;
+
+	/* Syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "select count(*) = 1 from pg_replication_slots where "
+					 "slot_type='physical' and slot_name=%s",
+					 quote_literal_cstr(PrimarySlotName));
+
+	res = walrcv_exec(wrconn, cmd.data, 1, slotRow);
+	pfree(cmd.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch primary_slot_name info from the "
+						"primary: %s", res->err)));
+
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	tuple_ok = tuplestore_gettupleslot(res->tuplestore, true, false, slot);
+	Assert(tuple_ok);			/* It must return one tuple */
+
+	valid = DatumGetBool(slot_getattr(slot, 1, &isnull));
+	Assert(!isnull);
+
+	if (!valid)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as slot specified in "
+					   "primary_slot_name is not valid"));
+
+	ExecClearTuple(slot);
+	walrcv_clear_result(res);
+	CommitTransactionCommand();
+}
+
+/*
+ * Checks if GUCs are set appropriately before starting slot sync worker
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+	/*
+	 * Since 'enable_syncslot' is ON, check that other GUC settings
+	 * (primary_slot_name, hot_standby_feedback, wal_level, primary_conninfo)
+	 * are compatible with slot synchronization. If not, raise ERROR.
+	 */
+
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as primary_slot_name is "
+					   "not set"));
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as hot_standby_feedback "
+					   "is off"));
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+				errmsg("exiting slots synchronisation as it requires "
+					   "wal_level >= logical"));
+
+	/*
+	 * The primary_conninfo is required to make connection to primary for
+	 * getting slots information.
+	 */
+	if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as primary_conninfo "
+					   "is not set"));
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	*dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (*dbname == NULL)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as dbname is not "
+					   "specified in primary_conninfo"));
+
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs changed, validate the values again
+ * through validate_slotsync_parameters() which will exit the worker
+ * if validaity fails.
+ */
+static void
+slotsync_reread_config(WalReceiverConn *wrconn)
+{
+	char	   *conninfo = pstrdup(PrimaryConnInfo);
+	char	   *slotname = pstrdup(PrimarySlotName);
+	bool		syncslot = enable_syncslot;
+	bool		standbyfeedback = hot_standby_feedback;
+	bool		revalidate = false;
+	char	   *dbname;
+	bool		conninfoChanged;
+	bool		slotnameChanged;
+
+	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	Assert(dbname);
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	conninfoChanged = strcmp(conninfo, PrimaryConnInfo) != 0;
+	slotnameChanged = strcmp(slotname, PrimarySlotName) != 0;
+
+	if (conninfoChanged || slotnameChanged ||
+		(syncslot != enable_syncslot) ||
+		(standbyfeedback != hot_standby_feedback))
+	{
+		revalidate = true;
+	}
+
+	pfree(conninfo);
+	pfree(slotname);
+
+	if (revalidate)
+	{
+		char	   *new_dbname;
+
+		validate_slotsync_parameters(&new_dbname);
+
+		/*
+		 * Since we have initialized this worker with old dbname, thus exit if
+		 * dbname changed. Let it get restarted and connect to new dbname
+		 * specified.
+		 */
+		if (conninfoChanged && strcmp(dbname, new_dbname) != 0)
+		{
+			ereport(ERROR,
+					errmsg("exiting slot sync woker as dbname in "
+						   "primary_conninfo changed"));
+		}
+
+		if (slotnameChanged)
+			validate_primary_slot(wrconn);
+	}
+}
+
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting"
+					   " down on receiving SIGINT"));
+
+		walrcv_disconnect(wrconn);
+		proc_exit(0);
+	}
+
+
+	if (ConfigReloadPending)
+		slotsync_reread_config(wrconn);
+}
+
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = 0;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	Assert(SlotSyncWorker->pid == 0);
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	validate_slotsync_parameters(&dbname);
+
+	/*
+	 * Connect to the database specified by user in primary_conninfo. We need
+	 * a database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/*
+	 * Connect to primary and validate the slot specified in
+	 * primary_slot_name.
+	 */
+	validate_primary_slot(wrconn);
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime;
+
+		ProcessSlotSyncInterrupts(wrconn);
+
+		naptime = synchronize_slots(wrconn);
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsSlotSyncWorker(void)
+{
+	return SlotSyncWorker->pid == MyProcPid;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (!SlotSyncWorker->pid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Wait for it to die. */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long. */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (!SlotSyncWorker->pid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Allocate and initialize slow sync worker shared memory
+ */
+void
+SlotSyncWorkerShmemInit(void)
+{
+	Size		size;
+	bool		found;
+
+	size = sizeof(SlotSyncWorkerCtx);
+	size = MAXALIGN(size);
+
+	SlotSyncWorker = (SlotSyncWorkerCtx *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
+
+	if (!found)
+	{
+		memset(SlotSyncWorker, 0, size);
+		SpinLockInit(&SlotSyncWorker->mutex);
+	}
+}
+
+/*
+ * Register the background worker for slots synchronization provided
+ * enable_syncslot is ON.
+ */
+void
+SlotSyncWorkerRegister(void)
+{
+	BackgroundWorker bgw;
+
+	if (!enable_syncslot)
+	{
+		ereport(LOG,
+				errmsg("skipping slots synchronization as enable_syncslot is "
+					   "disabled."));
+		return;
+	}
+
+	memset(&bgw, 0, sizeof(bgw));
+
+	/* We need database connection which needs shared-memory access as well. */
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+
+	/* Start as soon as a consistent state has been reached in a hot standby */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState_HotStandby;
+
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "replication slot sync worker");
+	snprintf(bgw.bgw_type, BGW_MAXLEN,
+			 "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
+	bgw.bgw_notify_pid = 0;
+	bgw.bgw_main_arg = (Datum) 0;
+
+	RegisterBackgroundWorker(&bgw);
+}
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index d2d8bf1a7a..f1675dbd85 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "commands/subscriptioncmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e46a1955e8..7b3784c212 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -141,7 +141,20 @@
  * subscribe to the new primary without losing any data.
  *
  * However, we do not enable failover for slots created by the table sync
- * worker.
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby due to the following reasons:
+ *
+ * - The standby needs to wait for the primary server to catch up because the
+ *   local restart_lsn of the newly created slot on the standby is set using
+ *   the latest redo position (GetXLogReplayRecPtr()), which is typically ahead
+ *   of the primary's restart_lsn.
+ * - The table sync slot's restart_lsn won't be advanced until the state
+ *   becomes SUBREL_STATE_CATCHUP.
+ *
+ * Therefore, if a failover happens before the restart_lsn advances, the table
+ * sync slot will not be synced to the standby. Consequently, we will not be
+ * able to subscribe to the promoted standby due to the absence of the
+ * necessary table sync slot.
  *
  * Additionally, failover is not enabled for the main slot if the table sync is
  * in progress. This is because if a failover occurs while the table sync
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index ca8b07a095..fe2503f37f 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -266,7 +267,7 @@ ReplicationSlotValidateName(const char *name, int elevel)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover)
+					  bool two_phase, bool failover, char sync_state)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -327,6 +328,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = sync_state;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -686,12 +688,26 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+	{
+		ReplicationSlotRelease();
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary.")));
+	}
+
 	ReplicationSlotDropAcquired();
 }
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index c87f61666d..e5ba5956aa 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -44,7 +44,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false);
+						  false, SYNCSLOT_STATE_NONE);
 
 	if (immediately_reserve)
 	{
@@ -137,7 +137,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover);
+						  failover, SYNCSLOT_STATE_NONE);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index b5493fcd69..6b65c3c7dc 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1071,7 +1071,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false);
+							  false, false, SYNCSLOT_STATE_NONE);
 
 		if (reserve_wal)
 		{
@@ -1102,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover);
+							  two_phase, failover, SYNCSLOT_STATE_NONE);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, true);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2225a4a6e6..76a0f9be58 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -36,6 +36,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -336,6 +337,7 @@ CreateOrAttachShmemStructs(void)
 	WalRcvShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7298a187d1..92ea349488 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -60,6 +60,7 @@
 #include "replication/logicalworker.h"
 #include "replication/slot.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
@@ -3286,6 +3287,17 @@ ProcessInterrupts(void)
 			 */
 			proc_exit(1);
 		}
+		else if (IsSlotSyncWorker())
+		{
+			ereport(DEBUG1,
+					(errmsg_internal("replication slot sync worker is shutting down due to administrator command")));
+
+			/*
+			 * Slot sync worker can be stopped at any time.
+			 * Use exit status 1 so the background worker is restarted.
+			 */
+			proc_exit(1);
+		}
 		else if (IsBackgroundWorker)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 4b776266a4..2b40d5db6e 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2021,6 +2022,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_POSTMASTER, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5d940b72cd..39224137e1 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -358,6 +358,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = off			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d906734750..6a8192ad83 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 214dc6c29e..75b4b2040d 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -17,6 +17,7 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"
 
 extern ObjectAddress CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										bool isTopLevel);
@@ -28,4 +29,7 @@ extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
 extern char defGetStreamingMode(DefElem *def);
 
+extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn,
+										char *slotname, bool missing_ok);
+
 #endif							/* SUBSCRIPTIONCMDS_H */
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index e90ff376a6..8559900b70 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -79,6 +79,7 @@ typedef enum
 	BgWorkerStart_PostmasterStart,
 	BgWorkerStart_ConsistentState,
 	BgWorkerStart_RecoveryFinished,
+	BgWorkerStart_ConsistentState_HotStandby,
 } BgWorkerStartTime;
 
 #define BGW_DEFAULT_RESTART_INTERVAL	60
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 5ddad69348..5fa7a18111 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -15,7 +15,6 @@
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "storage/spin.h"
-#include "replication/walreceiver.h"
 
 /*
  * Behaviour of replication slots, upon release or crash.
@@ -52,6 +51,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +119,13 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Is this a slot created by a sync-slot worker?
+	 *
+	 * Relevant for logical slots on the physical standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)? Only
 	 * relevant for logical slots on the primary server.
@@ -225,9 +239,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover);
+								  bool two_phase, bool failover,
+								  char sync_state);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
@@ -253,7 +268,6 @@ extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_l
 extern int	ReplicationSlotIndex(ReplicationSlot *slot);
 extern bool ReplicationSlotName(int index, Name name);
 extern void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot);
-extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok);
 
 extern void StartupReplicationSlots(void);
 extern void CheckPointReplicationSlots(bool is_shutdown);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index f1135762fb..c96a814b26 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -19,6 +19,7 @@
 #include "pgtime.h"
 #include "port/atomics.h"
 #include "replication/logicalproto.h"
+#include "replication/slot.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/latch.h"
@@ -279,6 +280,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -403,6 +419,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -428,6 +445,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 84bb79ac0f..16b1129fcd 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -237,6 +237,11 @@ extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
+/* Slot sync worker objects */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+extern PGDLLIMPORT bool enable_syncslot;
+
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
@@ -326,6 +331,13 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
+extern void SlotSyncWorkerRegister(void);
+extern void ShutDownSlotSync(void);
+extern void slotsync_drop_initiated_slots(void);
+extern bool IsSlotSyncWorker(void);
+extern void SlotSyncWorkerShmemInit(void);
+
 #define isParallelApplyWorker(worker) ((worker)->in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
 #define isTablesyncWorker(worker) ((worker)->in_use && \
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
index a00faf3e81..97538bdf1b 100644
--- a/src/test/recovery/t/050_standby_failover_slots_sync.pl
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -289,4 +289,131 @@ is( $primary->safe_psql(
 $subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
 
 
+# Test logical failover slots on the standby
+# Configure standby3 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub1_slot->| ----> subscriber1 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb3_slot--->| ----> standby3 (connected via streaming replication)
+#                                        |                 lsub1_slot(synced_slot)
+
+# Cleanup old standby_slot_names
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = ''
+));
+$primary->start;
+
+$primary->psql('postgres',
+		q{SELECT pg_create_physical_replication_slot('sb3_slot');});
+
+$backup_name = 'backup2';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+my $connstr_1 = $primary->connstr;
+$standby3->stop;
+$standby3->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb3_slot'
+});
+$standby3->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+$standby3->start;
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb3_slot'
+));
+$primary->start;
+
+# Restart the standby
+$standby3->restart;
+
+# Wait for the standby to start sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots;}
+  ),
+  'lsub1_slot',
+  'failover slot was created');
+
+# Verify slot properties on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|r",
+  'logical slot has sync_state as ready and failover as true on standby');
+
+# Verify slot properties on the primary
+is( $primary->safe_psql('postgres',
+    q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|n",
+  'logical slot has sync_state as none and failover as true on primary');
+
+# Test to confirm that restart_lsn of the logical slot on the primary is synced to the standby
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_int;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# let the slots get synced on the standby
+sleep 2;
+
+# Get the restart_lsn for the logical slot lsub1_slot on the primary
+my $primary_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that restart_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'restart_lsn of slot lsub1_slot synced to standby');
+
+# Get the confirmed_flush_lsn for the logical slot lsub1_slot on the primary
+$primary_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that confirmed_flush_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub1_slot synced to the standby');
+
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 175bcecfd1..9a79feaddf 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2317,6 +2317,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2575,6 +2576,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorkerCtx
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.30.0.windows.2



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 11:36                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-08 12:05                                             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-12-11 00:16                                               ` Peter Smith <[email protected]>
  2023-12-11 02:19                                                 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Peter Smith @ 2023-12-11 00:16 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; +Cc: Amit Kapila <[email protected]>; shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

FYI -- the patch 0002 did not apply cleanly for me on top of the 050
test file created by patch 0001.

[postgres@CentOS7-x64 oss_postgres_misc]$ git apply
../patches_misc/v44-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch

[postgres@CentOS7-x64 oss_postgres_misc]$ git apply
../patches_misc/v44-0002-Add-logical-slot-sync-capability-to-the-physical.patch
error: patch failed: src/test/recovery/t/050_standby_failover_slots_sync.pl:289
error: src/test/recovery/t/050_standby_failover_slots_sync.pl: patch
does not apply

======
Kind Regards,
Peter Smith.
Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 11:36                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-08 12:05                                             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-11 00:16                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
@ 2023-12-11 02:19                                                 ` Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-11 07:32                                                   ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-12-11 02:19 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; +Cc: Amit Kapila <[email protected]>; shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Monday, December 11, 2023 8:17 AM Peter Smith <[email protected]> wrote:
> 
> FYI -- the patch 0002 did not apply cleanly for me on top of the 050 test file
> created by patch 0001.
> 
> [postgres@CentOS7-x64 oss_postgres_misc]$ git
> apply ../patches_misc/v44-0001-Allow-logical-walsenders-to-wait-for-the-ph
> ysica.patch
> 
> [postgres@CentOS7-x64 oss_postgres_misc]$ git
> apply ../patches_misc/v44-0002-Add-logical-slot-sync-capability-to-the-phy
> sical.patch
> error: patch failed: src/test/recovery/t/050_standby_failover_slots_sync.pl:289
> error: src/test/recovery/t/050_standby_failover_slots_sync.pl: patch does not
> apply

Thanks for reporting. Here is the rebased patch set V44_2.
(There are no code changes in this version.)

Best Regards,
Hou zj


Attachments:

  [application/octet-stream] v44_2-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch (7.9K, ../../OS0PR01MB571668514ACA508DCC08E957948FA@OS0PR01MB5716.jpnprd01.prod.outlook.com/2-v44_2-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch)
  download | inline diff:
From 775781cef08ea90d6744d01306269ec16338418a Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Wed, 6 Dec 2023 15:58:21 +0530
Subject: [PATCH v44 3/3] Allow slot-sync worker to wait for the cascading
 standbys.

The GUC standby_slot_names is needed to be set on first standby
in order to allow it to wait for confirmation for cascading
standbys before updating logical 'synced' slots in slot-sync worker.
The intent is that the logical slots (synced ones) should not go
ahead of cascading standbys.

For the user created slots on first standby, we already have this wait
logic in place in logical walsender and in pg_logical_slot_get_changes_guts(),
but for synced slots (which can not be consumed yet), we need to make
sure that they are not going ahead of cascading standbys and that is
acheived by introducing the wait in slot-sync worker before we actually
update the slots.
---
 src/backend/replication/logical/slotsync.c | 89 ++++++++++++++++++++--
 src/backend/replication/walsender.c        |  4 +-
 src/include/replication/walsender.h        |  5 ++
 3 files changed, 88 insertions(+), 10 deletions(-)

diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index e01c3cce42..fa46d9f0e9 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -108,7 +108,9 @@ static TimestampTz last_update_time;
  */
 #define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
 
-static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn);
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn,
+									  List **standby_slots);
+
 
 /*
  * Wait for remote slot to pass locally reserved position.
@@ -164,7 +166,7 @@ wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
 		CHECK_FOR_INTERRUPTS();
 
 		/* Handle any termination request if any */
-		ProcessSlotSyncInterrupts(wrconn);
+		ProcessSlotSyncInterrupts(wrconn, NULL /* standby_slots */ );
 
 		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
 
@@ -488,6 +490,52 @@ construct_slot_query(StringInfo s)
 					 " WHERE failover and sync_state != 'i'");
 }
 
+/*
+ * Wait for cascading physical standbys corresponding to physical slots
+ * specified in standby_slot_names GUC to confirm receiving given lsn.
+ */
+static void
+wait_for_standby_confirmation(XLogRecPtr wait_for_lsn,
+							  WalReceiverConn *wrconn)
+{
+	List	   *standby_slots;
+
+	/* Nothing to be done */
+	if (strcmp(standby_slot_names, "") == 0)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	for (;;)
+	{
+		int			rc;
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		/*
+		 * This will reload configuration and will refresh the standby_slots
+		 * as well provided standby_slot_names GUC is changed by the user.
+		 */
+		ProcessSlotSyncInterrupts(wrconn, &standby_slots);
+
+		/*
+		 * XXX: Is waiting for 5 second before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   5000L,
+					   WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+}
+
 /*
  * Synchronize single slot to given position.
  *
@@ -768,6 +816,7 @@ synchronize_slots(WalReceiverConn *wrconn)
 	List	   *remote_slot_list = NIL;
 	MemoryContext oldctx = CurrentMemoryContext;
 	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	XLogRecPtr	max_confirmed_lsn = 0;
 	ListCell   *cell;
 	bool		slot_updated = false;
 	TimestampTz now;
@@ -825,6 +874,9 @@ synchronize_slots(WalReceiverConn *wrconn)
 		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
 		Assert(!isnull);
 
+		if (remote_slot->confirmed_lsn > max_confirmed_lsn)
+			max_confirmed_lsn = remote_slot->confirmed_lsn;
+
 		/*
 		 * It is possible to get null values for LSN and Xmin if slot is
 		 * invalidated on the primary server, so handle accordingly.
@@ -868,6 +920,17 @@ synchronize_slots(WalReceiverConn *wrconn)
 	 */
 	drop_obsolete_slots(remote_slot_list);
 
+	/*
+	 * If there are cascading standbys, wait for their confirmation before we
+	 * update synced logical slots locally.
+	 *
+	 * Instead of waiting on confirmation for lsn of each slot, let us wait
+	 * once for confirmation on max_confirmed_lsn. If that is confirmed by
+	 * each cascading standby, we are good to update all the slots.
+	 */
+	if (remote_slot_list)
+		wait_for_standby_confirmation(max_confirmed_lsn, wrconn);
+
 	/* Now sync the slots locally */
 	foreach(cell, remote_slot_list)
 	{
@@ -1043,7 +1106,7 @@ validate_slotsync_parameters(char **dbname)
  * if validaity fails.
  */
 static void
-slotsync_reread_config(WalReceiverConn *wrconn)
+slotsync_reread_config(WalReceiverConn *wrconn, List **standby_slots)
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 	char	   *slotname = pstrdup(PrimarySlotName);
@@ -1057,8 +1120,14 @@ slotsync_reread_config(WalReceiverConn *wrconn)
 	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
 	Assert(dbname);
 
-	ConfigReloadPending = false;
-	ProcessConfigFile(PGC_SIGHUP);
+	/*
+	 * Reload configs and recreate the standby_slot_names_list if GUC
+	 * standby_slot_names changed.
+	 */
+	if (standby_slots)
+		WalSndRereadConfigAndReInitSlotList(standby_slots);
+	else
+		ProcessConfigFile(PGC_SIGHUP);
 
 	conninfoChanged = strcmp(conninfo, PrimaryConnInfo) != 0;
 	slotnameChanged = strcmp(slotname, PrimarySlotName) != 0;
@@ -1101,7 +1170,8 @@ slotsync_reread_config(WalReceiverConn *wrconn)
  * Interrupt handler for main loop of slot sync worker.
  */
 static void
-ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn,
+						  List **standby_slots)
 {
 	CHECK_FOR_INTERRUPTS();
 
@@ -1117,7 +1187,10 @@ ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
 
 
 	if (ConfigReloadPending)
-		slotsync_reread_config(wrconn);
+	{
+		ConfigReloadPending = false;
+		slotsync_reread_config(wrconn, standby_slots);
+	}
 }
 
 /*
@@ -1191,7 +1264,7 @@ ReplSlotSyncWorkerMain(Datum main_arg)
 		int			rc;
 		long		naptime;
 
-		ProcessSlotSyncInterrupts(wrconn);
+		ProcessSlotSyncInterrupts(wrconn, NULL /* standby_slots */ );
 
 		naptime = synchronize_slots(wrconn);
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 6b65c3c7dc..5ea47430a7 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1609,7 +1609,7 @@ PhysicalWakeupLogicalWalSnd(void)
  * Reload the config file and reinitialize the standby slot list if the GUC
  * standby_slot_names has changed.
  */
-static void
+void
 WalSndRereadConfigAndReInitSlotList(List **standby_slots)
 {
 	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
@@ -1634,7 +1634,7 @@ WalSndRereadConfigAndReInitSlotList(List **standby_slots)
  * it removes slots that have been invalidated, dropped, or converted to
  * logical slots.
  */
-static void
+void
 WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 {
 	ListCell   *lc;
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index ecd212c7b6..07b2c719ae 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -13,6 +13,7 @@
 #define _WALSENDER_H
 
 #include "access/xlogdefs.h"
+#include "nodes/pg_list.h"
 
 /*
  * What to do with a snapshot in create replication slot command.
@@ -48,7 +49,11 @@ extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn,
+									 List **standby_slots);
 extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
+extern List *WalSndGetStandbySlots(void);
+extern void WalSndRereadConfigAndReInitSlotList(List **standby_slots);
 
 /*
  * Remember that we want to wakeup walsenders later
-- 
2.30.0.windows.2



  [application/octet-stream] v44_2-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (145.9K, ../../OS0PR01MB571668514ACA508DCC08E957948FA@OS0PR01MB5716.jpnprd01.prod.outlook.com/3-v44_2-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From c3ea141cfd5404c144aabbe2ff86e255b7d5d431 Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Wed, 6 Dec 2023 15:52:16 +0530
Subject: [PATCH v44 1/3] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to
the physical standbys so that logical replication can be resumed after
failover. It is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API.

Ex1:
CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

Ex2: (failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

A new replication command called ALTER_REPLICATION_SLOT and a
corresponding walreceiver API function named walrcv_alter_slot have been
implemented. They allow subscribers or users to modify the failover
property of a replication slot on the publisher.

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).
---
 contrib/test_decoding/expected/slot.out       |  58 +++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |  20 +-
 doc/src/sgml/ref/create_subscription.sgml     |  23 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       | 110 +++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  38 +-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  53 ++-
 src/backend/replication/logical/worker.c      |  67 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 182 ++++++++-
 src/backend/replication/slotfuncs.c           |  25 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 344 +++++++++++++++++-
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_dump/pg_dump.c                     |  20 +-
 src/bin/pg_dump/pg_dump.h                     |   1 +
 src/bin/pg_upgrade/info.c                     |   5 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 .../t/050_standby_failover_slots_sync.pl      | 298 +++++++++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 +++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 50 files changed, 1540 insertions(+), 170 deletions(-)
 create mode 100644 src/test/recovery/t/050_standby_failover_slots_sync.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 94d1eb2b81..30d9b53e03 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4360,6 +4360,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..481e397bad 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
@@ -230,6 +233,17 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
+
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property of the new slot may differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription. When creating the slot,
+      ensure the slot failover property matches the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter value of the subscription.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..fa6cd1c43f 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,29 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..67826b89c2 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,34 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If the slot_name is specified without the create_slot option,
+			 * it is possible that the user intends to use an existing slot on
+			 * the publisher, so here we alter the failover property of the
+			 * slot to match the failover value in subscription.
+			 *
+			 * We do not need to change the failover to false if the server
+			 * does not support failover (e.g. pre-PG17)
+			 */
+			else if (opts.slot_name &&
+					 (failover_enabled || walrcv_server_version(wrconn) >= 170000))
+			{
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+								opts.slot_name,
+								failover_enabled ? "true" : "false")));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,13 +1331,22 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
@@ -1334,13 +1395,26 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 						/* translator: %s is an SQL ALTER command */
 								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
 										 isadd ?
@@ -1389,7 +1463,19 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 					ereport(ERROR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "two_phase"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "failover"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 4152d1ddbc..ff65fdb4d9 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -888,8 +892,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -918,7 +922,8 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+			appendStringInfoString(&cmd, "FAILOVER, ");
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -987,6 +992,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index df3c42eb5d..d2d8bf1a7a 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -614,15 +614,28 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "two_phase")));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "failover")));
+
 				should_exit = true;
 			}
 		}
@@ -1420,7 +1433,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1722,10 +1736,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1733,9 +1749,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1749,9 +1764,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..e46a1955e8 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,6 +132,33 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * failover = true when creating the subscription. Enabling failover allows us
+ * to smoothly transition to the promoted standby, ensuring that we can
+ * subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
@@ -3947,6 +3974,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4510,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4568,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4608,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..ca8b07a095 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -248,10 +260,13 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     during getting changes, if the two_phase option is enabled it can skip
  *     prepare because by that time start decoding point has been moved. So the
  *     user will only get commit prepared.
+ * failover: Allows the slot to be synced to physical standbys so that logical
+ *     replication can be resumed after failover.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +695,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2200,138 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+	bool		ok = true;
+
+	/* Need a modifiable copy of string */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into list of identifiers */
+	if (!(ok = SplitIdentifierString(rawname, ',', &elemlist)))
+		GUC_check_errdetail("List syntax is invalid.");
+
+	/*
+	 * If there is a syntax error in the name or if the replication slots'
+	 * data is not initialized yet (i.e., we are in the startup process), skip
+	 * the slot verification.
+	 */
+	if (!ok || !ReplicationSlotCtl)
+	{
+		pfree(rawname);
+		list_free(elemlist);
+		return ok;
+	}
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			GUC_check_errdetail("replication slot \"%s\" does not exist",
+								name);
+			ok = false;
+			break;
+		}
+
+		if (!SlotIsPhysical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			ok = false;
+			break;
+		}
+	}
+
+	pfree(rawname);
+	list_free(elemlist);
+	return ok;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "list syntax is invalid");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..c87f61666d 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -491,6 +501,12 @@ pg_logical_replication_slot_advance(XLogRecPtr moveto)
 											   .segment_close = wal_segment_close),
 									NULL, NULL, NULL);
 
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to moveto lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(moveto);
+
 		/*
 		 * Start reading at the slot's restart_lsn, which we know to point to
 		 * a valid record.
@@ -679,6 +695,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +751,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +791,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 26ded928a7..ca61a99785 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -387,7 +387,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..b5493fcd69 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool		failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,243 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up the logical walsender processes with failover-enabled slots if the
+ * physical slot of the current walsender is specified in standby_slot_names
+ * GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn. Additionally,
+ * it removes slots that have been invalidated, dropped, or converted to
+ * logical slots.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
+		bool		invalidated = false;
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (slot && SlotIsPhysical(slot))
+		{
+			SpinLockAcquire(&slot->mutex);
+			restart_lsn = slot->data.restart_lsn;
+			invalidated = slot->data.invalidated != RS_INVAL_NONE;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Continue if the current slot hasn't caught up. */
+		if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
+			restart_lsn < wait_for_lsn)
+		{
+			/* Log warning if no active_pid for this physical slot */
+			if (slot->active_pid == 0)
+				ereport(WARNING,
+						errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+							   name, "standby_slot_names"),
+						errdetail("Logical replication is waiting on the "
+								  "standby associated with \"%s\"", name),
+						errhint("Consider starting standby associated with "
+								"\"%s\" or amend standby_slot_names", name));
+
+			continue;
+		}
+		else if (!slot)
+		{
+			/*
+			 * It may happen that the slot specified in standby_slot_names GUC
+			 * value is dropped, so let's skip over it.
+			 */
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+		}
+		else if (SlotIsLogical(slot))
+		{
+			/*
+			 * If a logical slot name is provided in standby_slot_names, issue
+			 * a WARNING and skip it. Although logical slots are disallowed in
+			 * the GUC check_hook(validate_standby_slots), it is still
+			 * possible for a user to drop an existing physical slot and
+			 * recreate a logical slot with the same name. Since it is
+			 * harmless, a WARNING should be enough, no need to error-out.
+			 */
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+		}
+		else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
+		{
+			/*
+			 * Specified physical slot may have been invalidated, so no point
+			 * in waiting for it.
+			 */
+			warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+		}
+		else
+		{
+			Assert(restart_lsn >= wait_for_lsn);
+		}
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving the given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, we also
+ * wait for all the specified streaming replication standby servers to
+ * confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr even when we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
-	if (RecentFlushPtr != InvalidXLogRecPtr &&
-		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	if (!XLogRecPtrIsInvalid(RecentFlushPtr) && loc <= RecentFlushPtr)
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know
+		 * we have enough WAL available and all the standby servers have
+		 * confirmed receipt of WAL up to RecentFlushPtr. This is particularly
+		 * interesting if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1835,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1850,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,9 +1889,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
+		{
+			/* already caught up and doesn't need to wait for standby_slots */
 			break;
+		}
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
 		WalSndCaughtUp = true;
@@ -1654,9 +1940,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2107,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2344,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3607,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3678,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another
+	 * CV that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 6474e35ec0..4b776266a4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4572,6 +4572,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..5d940b72cd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -329,6 +329,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8c0b5486b9..373c2514df 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4618,6 +4618,7 @@ getSubscriptions(Archive *fout)
 	int			i_subsynccommit;
 	int			i_subpublications;
 	int			i_suborigin;
+	int			i_subfailoverstate;
 	int			i,
 				ntups;
 
@@ -4673,14 +4674,22 @@ getSubscriptions(Archive *fout)
 		appendPQExpBufferStr(query,
 							 " s.subpasswordrequired,\n"
 							 " s.subrunasowner,\n"
-							 " s.suborigin\n");
+							 " s.suborigin,\n");
 	else
 		appendPQExpBuffer(query,
 						  " 't' AS subpasswordrequired,\n"
 						  " 't' AS subrunasowner,\n"
-						  " '%s' AS suborigin\n",
+						  " '%s' AS suborigin,\n",
 						  LOGICALREP_ORIGIN_ANY);
 
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(query,
+							 " s.subfailoverstate\n");
+	else
+		appendPQExpBuffer(query,
+						  " '%c' AS subfailoverstate\n",
+						  LOGICALREP_FAILOVER_STATE_DISABLED);
+
 	appendPQExpBufferStr(query,
 						 "FROM pg_subscription s\n"
 						 "WHERE s.subdbid = (SELECT oid FROM pg_database\n"
@@ -4709,6 +4718,7 @@ getSubscriptions(Archive *fout)
 	i_subsynccommit = PQfnumber(res, "subsynccommit");
 	i_subpublications = PQfnumber(res, "subpublications");
 	i_suborigin = PQfnumber(res, "suborigin");
+	i_subfailoverstate = PQfnumber(res, "subfailoverstate");
 
 	subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo));
 
@@ -4746,6 +4756,8 @@ getSubscriptions(Archive *fout)
 		subinfo[i].subpublications =
 			pg_strdup(PQgetvalue(res, i, i_subpublications));
 		subinfo[i].suborigin = pg_strdup(PQgetvalue(res, i, i_suborigin));
+		subinfo[i].subfailoverstate =
+			pg_strdup(PQgetvalue(res, i, i_subfailoverstate));
 
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(subinfo[i].dobj), fout);
@@ -4771,6 +4783,7 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	int			npubnames = 0;
 	int			i;
 	char		two_phase_disabled[] = {LOGICALREP_TWOPHASE_STATE_DISABLED, '\0'};
+	char		failover_disabled[] = {LOGICALREP_FAILOVER_STATE_DISABLED, '\0'};
 
 	/* Do nothing in data-only dump */
 	if (dopt->dataOnly)
@@ -4818,6 +4831,9 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	if (strcmp(subinfo->subtwophasestate, two_phase_disabled) != 0)
 		appendPQExpBufferStr(query, ", two_phase = on");
 
+	if (strcmp(subinfo->subfailoverstate, failover_disabled) != 0)
+		appendPQExpBufferStr(query, ", failover = true");
+
 	if (strcmp(subinfo->subdisableonerr, "t") == 0)
 		appendPQExpBufferStr(query, ", disable_on_error = true");
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2fe3cbed9a..f62a4dfd4b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -671,6 +671,7 @@ typedef struct _SubscriptionInfo
 	char	   *subsynccommit;
 	char	   *subpublications;
 	char	   *suborigin;
+	char	   *subfailoverstate;
 } SubscriptionInfo;
 
 /*
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..e16286f18c 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,6 +679,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
@@ -687,6 +688,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +699,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..2d8bcb26f9 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced to the
+								 * physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 020e7aa1cc..cb3a2b3aed 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -159,7 +159,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -179,8 +179,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 049801186c..905964a2e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3327,7 +3327,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb58dee3bc..d906734750 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..5ddad69348 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)? Only
+	 * relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 949e874f21..f1135762fb 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -355,9 +355,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									  const char *slotname,
+									  bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -399,6 +410,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -428,8 +440,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 60313980a9..ecd212c7b6 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -12,6 +12,8 @@
 #ifndef _WALSENDER_H
 #define _WALSENDER_H
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -45,6 +47,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index db73408937..84bb79ac0f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -256,7 +256,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..3be3ee52fc 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_verify_slot_order.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
new file mode 100644
index 0000000000..09b5d006a5
--- /dev/null
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -0,0 +1,298 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+##################################################
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+##################################################
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres',
+	"CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+$subscriber1->safe_psql('postgres',
+	"CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true
+$subscriber1->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);"
+);
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql('postgres',
+	"CREATE TABLE tab_int (a int PRIMARY KEY);");
+$subscriber2->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub2 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);");
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby comes
+# up.
+$standby1->stop;
+
+# Create some data on primary
+my $primary_row_count   = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('regress_mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary until standby1 acknowledges changes"
+);
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 acknowledges changes");
+
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby slot's
+# restart_lsn is advanced or the slots is removed from the standby_slot_names
+# list
+$publisher->safe_psql('postgres', "TRUNCATE tab_int;");
+$publisher->wait_for_catchup('regress_mysub1');
+$standby1->stop;
+
+##################################################
+# Verify that when using pg_logical_slot_get_changes to consume changes from a
+# logical slot with failover enabled, it will also wait for the slots specified
+# in standby_slot_names to catch up.
+##################################################
+
+# Create a logical 'test_decoding' replication slot with failover enabled
+$publisher->safe_psql('postgres',
+	"SELECT pg_create_logical_replication_slot('test_slot', 'test_decoding', false, false, true);"
+);
+
+my $back_q = $primary->background_psql('postgres', on_error_stop => 0);
+my $pid = $back_q->query('SELECT pg_backend_pid()');
+
+# Try and get changes from the logical slot with failover enabled.
+my $offset = -s $primary->logfile;
+$back_q->query_until(qr//,
+	"SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL);\n");
+
+# Wait until the primary server logs a warning indicating that it is waiting
+# for the sb1_slot to catch up.
+$primary->wait_for_log(
+	qr/WARNING: ( [A-Z0-9]+:)? replication slot \"sb1_slot\" specified in parameter \"standby_slot_names\" does not have active_pid/,
+	$offset);
+
+ok($primary->safe_psql('postgres', "SELECT pg_cancel_backend($pid)"),
+	"cancelling pg_logical_slot_get_changes command");
+
+$publisher->safe_psql('postgres',
+	"SELECT pg_drop_replication_slot('test_slot');"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we manually advance this slot's LSN to the
+# latest position.
+##################################################
+
+# Create some data on primary
+$primary_row_count = 10;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# The subscription that's up and running and is enabled for failover doesn't
+# get the data from primary and keeps waiting for the standby specified in
+# standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary as long as standby1 restart_lsn has not been updated"
+);
+
+# Advance the lsn of the standby slot manually
+$primary->safe_psql('postgres',
+	"SELECT * FROM pg_replication_slot_advance('sb1_slot', pg_current_wal_lsn());"
+);
+
+# Now that the standby lsn has advanced, primary must send the decoded
+# changes to the subscription
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1's restart_lsn has been updated"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we remove the slot from standby_slot_names.
+##################################################
+
+# Create some data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(11,20);");
+
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 10 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data as the sb1_slot doesn't catch up");
+
+# Remove the standby from the standby_slot_names list and reload the
+# configuration
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', "''");
+$primary->psql('postgres', "SELECT pg_reload_conf()");
+
+# Now that the standby lsn has advanced, primary must send the decoded
+# changes to the subscription.
+$publisher->wait_for_catchup('regress_mysub1');
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 20 FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 is removed from the standby_slot_names list"
+);
+
+# Put the standby back on the primary_slot_name for the rest of the tests
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', 'sb1_slot');
+$primary->restart();
+
+##################################################
+# Test that when a subscription with failover enabled is created, it will alter
+# the failover property of the corresponding slot on the publisher.
+##################################################
+
+# Create a slot on the publisher with failover disabled
+$primary->safe_psql('postgres',
+	"SELECT 'init' FROM pg_create_logical_replication_slot('lsub3_slot', 'pgoutput', false, false, false);"
+);
+
+# Confirm that the failover flag on the slot is turned off
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"f",
+	'logical slot has failover false on primary');
+
+# Create another subscription enabling failover
+$subscriber1->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub3 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub3_slot, copy_data=false, failover = true, create_slot = false);"
+);
+
+# Confirm that the failover flag on the slot has now been turned on
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"t",
+	'logical slot has failover true on primary');
+
+$subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 38a86575e1..175bcecfd1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3865,6 +3866,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.30.0.windows.2



  [application/octet-stream] v44_2-0002-Add-logical-slot-sync-capability-to-the-physical.patch (84.6K, ../../OS0PR01MB571668514ACA508DCC08E957948FA@OS0PR01MB5716.jpnprd01.prod.outlook.com/4-v44_2-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From 5fa7276cccae7db7c244149398cb56fe0ce705cf Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Mon, 11 Dec 2023 10:01:17 +0800
Subject: [PATCH v44 2/3] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover. All the failover logical
replication slots on the primary (assuming configurations are
appropriate) are automatically created on the physical standbys and
are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The nap time of worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. And if
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated.

If a logical slot on the primary is valid but is invalidated on the standby,
then that slot is dropped and recreated on the standby in next sync-cycle. It
is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently). This situation may occur due to the
following reasons:
- The max_slot_wal_keep_size on the standby is insufficient to retain WAL
  records from the restart_lsn of the slot.
- primary_slot_name is temporarily reset to null and the physical slot is
  removed.
- The primary changes wal_level to a level lower than logical.

Slots synced on the standby can be identified using 'sync_state' column of
pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but waiting for the remote slot on the
     primary server to catch up.
'r': ready for periodic syncs.
---
 doc/src/sgml/bgworker.sgml                    |   16 +-
 doc/src/sgml/config.sgml                      |   35 +-
 doc/src/sgml/logicaldecoding.sgml             |   34 +
 doc/src/sgml/system-views.sgml                |   25 +
 src/backend/access/transam/xlogrecovery.c     |   10 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    4 +
 src/backend/postmaster/postmaster.c           |    6 +
 .../libpqwalreceiver/libpqwalreceiver.c       |   42 +
 src/backend/replication/logical/Makefile      |    1 +
 src/backend/replication/logical/logical.c     |   20 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1329 +++++++++++++++++
 src/backend/replication/logical/tablesync.c   |    1 +
 src/backend/replication/logical/worker.c      |   15 +-
 src/backend/replication/slot.c                |   20 +-
 src/backend/replication/slotfuncs.c           |   37 +-
 src/backend/replication/walsender.c           |    6 +-
 src/backend/storage/ipc/ipci.c                |    2 +
 src/backend/tcop/postgres.c                   |   12 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   10 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/commands/subscriptioncmds.h       |    4 +
 src/include/postmaster/bgworker.h             |    1 +
 src/include/replication/slot.h                |   22 +-
 src/include/replication/walreceiver.h         |   19 +
 src/include/replication/worker_internal.h     |   12 +
 .../t/050_standby_failover_slots_sync.pl      |  127 ++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    2 +
 33 files changed, 1808 insertions(+), 29 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2c393385a9..377f301b63 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -121,11 +121,17 @@ typedef struct BackgroundWorker
    <literal>BgWorkerStart_ConsistentState</literal> (start as soon as a consistent state
    has been reached in a hot standby, allowing processes to connect to
    databases and run read-only queries), and
-   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system has
-   entered normal read-write state).  Note the last two values are equivalent
-   in a server that's not a hot standby.  Note that this setting only indicates
-   when the processes are to be started; they do not stop when a different state
-   is reached.
+   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system
+   has entered normal read-write state. Note that the
+   <literal>BgWorkerStart_ConsistentState</literal> and
+   <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
+   in a server that's not a hot standby), and
+   <literal>BgWorkerStart_ConsistentState_HotStandby</literal> (same meaning as
+   <literal>BgWorkerStart_ConsistentState</literal> but it is more strict in
+   terms of the server i.e. start the  worker only if it is hot-standby; if it is
+   consistent state in non-standby, worker will not be started). Note that this
+   setting only indicates when the processes are to be started; they do not stop
+   when a different state is reached.
   </para>
 
   <para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 30d9b53e03..1977962caf 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4373,6 +4373,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must enable
+        <varname>enable_syncslot</varname> for the standbys to receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4566,10 +4572,15 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           A password needs to be provided too, if the sender demands password
           authentication.  It can be provided in the
           <varname>primary_conninfo</varname> string, or in a separate
-          <filename>~/.pgpass</filename> file on the standby server (use
+          <filename>~/.pgpass</filename> file on the standby server. (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          Specify <literal>dbname</literal> in
+          <varname>primary_conninfo</varname> string to allow synchronization
+          of slots from the primary server to the standby server.
+          This will only be used for slot synchronization. It is ignored
+          for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4894,6 +4905,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..0defa0ec2b 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,40 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and set
+     <varname>enable_syncslot</varname> on the standby. For the synchronization
+     to work, it is mandatory to have physical replication slot between the
+     primary and the standby. This physical replication slot for the standby
+     should be listed in <varname>standby_slot_names</varname> on the primary
+     to prevent the subscriber from consuming changes faster than the hot
+     standby. Additionally, similar to creating a logical replication slot
+     on the hot standby, <varname>hot_standby_feedback</varname> should be
+     set on the standby and a physical slot between the primary and the standby
+     should be used.
+    </para>
+
+    <para>
+     By enabling synchronization of slots, logical replication can be resumed
+     after failover depending upon the
+     <link linkend="view-pg-replication-slots">pg_replication_slots</link>.<structfield>sync_state</structfield>
+     for the synchronized slots on the standby at the time of failover.
+     The slots which were in ready sync_state ('r') on the standby before
+     failover can be used for logical replication after failover. However,
+     the slots which were in initiated sync_state ('i) and were not
+     sync-ready ('r') at the time of failover will be dropped and logical
+     replication for such slots can not be resumed after failover. This applies
+     to the case where a logical subscription is disabled before failover and is
+     enabled after failover. If the synchronized slot due to disabled
+     subscription could not be made sync-ready ('r') on standby, then the
+     subscription can not be resumed after failover even when enabled.
+     If the primary is idle, making the synchronized slot on the standby
+     as sync-ready ('r') for enabled subscription may take noticeable time.
+     This can be sped up by calling the
+     <function>pg_log_standby_snapshot</function> function on the primary.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..3f6e2f82c8 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,31 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has enabled slots synchronization.
+      </para>
+      <para>
+       State code:
+       <literal>n</literal> = none for user created slots,
+       <literal>i</literal> = sync initiated for the slot but slot is not ready
+        yet for periodic syncs,
+       <literal>r</literal> = ready for periodic syncs.
+      </para>
+      <para>
+      The hot standby can have any of these sync_state for the slots but on a
+      hot standby, the slots with state 'r' and 'i' can neither be used for logical
+      decoded nor dropped by the user. The primary server will have sync_state
+      as 'n' for all the slots. But if the standby is promoted to become the
+      new primary server, sync_state can be seen 'r' as well. On this new
+      primary server, slots with sync_state as 'r' and 'n' will behave the same.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c61566666a..9fb09ba0fc 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1436,15 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index c345639086..854c967910 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -22,6 +22,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "replication/worker_internal.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -130,6 +131,9 @@ static const struct
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index ae31d66930..d75bb378b7 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -117,6 +117,7 @@
 #include "postmaster/syslogger.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -1006,6 +1007,8 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	SlotSyncWorkerRegister();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
@@ -5743,6 +5746,9 @@ bgworker_should_start_now(BgWorkerStartTime start_time)
 		case PM_HOT_STANDBY:
 			if (start_time == BgWorkerStart_ConsistentState)
 				return true;
+			else if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
+					 pmState != PM_RUN)
+				return true;
 			/* fall through */
 
 		case PM_RECOVERY:
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index ff65fdb4d9..7e1492f046 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -418,6 +421,45 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	PQconninfoOption *opt;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..7de68a61e2 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,26 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	/*
+	 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+	 * promotion.
+	 */
+	if (!RecoveryInProgress() && slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+		elog(ERROR, "replication slot \"%s\" was not synced completely from the primary server",
+			 NameStr(slot->data.name));
+
+	/*
+	 * Do not allow consumption of a "synchronized" slot until the standby
+	 * gets promoted.
+	 */
+	if (RecoveryInProgress() && slot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot use replication slot \"%s\" for logical decoding",
+						NameStr(slot->data.name)),
+				 errdetail("This slot is being synced from the primary server."),
+				 errhint("Specify another replication slot.")));
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..e01c3cce42
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1329 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * It also takes care of dropping the slots which were created by it and are
+ * currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/table.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_database.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/*
+ * Struct for sharing information between startup process and slot
+ * sync worker.
+ *
+ * Slot sync worker's pid is needed by startup process in order to
+ * shut it down during promotion.
+ */
+typedef struct SlotSyncWorkerCtx
+{
+	pid_t		pid;
+	slock_t		mutex;
+} SlotSyncWorkerCtx;
+
+SlotSyncWorkerCtx *SlotSyncWorker = NULL;
+
+/* GUC variable */
+bool		enable_syncslot = false;
+
+/* The last sync-cycle time when the worker updated any of the slots. */
+static TimestampTz last_update_time;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+/*
+ * Number of attempts for wait_for_primary_slot_catchup() after
+ * which it aborts the wait and the slot sync worker then moves
+ * to the next slot creation/sync.
+ */
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
+
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn);
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * *persist will be set to false if the slot has disappeared or was invalidated
+ * on the primary; otherwise, it will be set to true.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *persist)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+	if (persist)
+		*persist = true;
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn, confirmed_flush_lsn,"
+					 " catalog_xmin FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		XLogRecPtr	new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *slot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Handle any termination request if any */
+		ProcessSlotSyncInterrupts(wrconn);
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot info for slot \"%s\" from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" disappeared from the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(slot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" invalidated on the primary server,"
+							" slot creation aborted", remote_slot->name)));
+			pfree(cmd.data);
+			ExecClearTuple(slot);
+			walrcv_clear_result(res);
+
+			/*
+			 * The slot being created will be dropped when it is released (see
+			 * ReplicationSlotRelease).
+			 */
+			if (persist)
+				*persist = false;
+
+			return false;
+		}
+
+		/*
+		 * Once we got valid restart_lsn, then confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(slot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\"",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up.
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			slots = lappend(slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(slots);
+}
+
+/*
+ * Get list of local logical slot names which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slot_names(void)
+{
+	List	   *localSyncedSlots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			localSyncedSlots = lappend(localSyncedSlots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return localSyncedSlots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+check_sync_slot_validity(ReplicationSlot *local_slot, List *remote_slots,
+						 bool *locally_invalidated)
+{
+	ListCell   *cell;
+
+	foreach(cell, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			SpinLockAcquire(&local_slot->mutex);
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+			SpinLockRelease(&local_slot->mutex);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Additionally, it drops slots that are valid on the primary but got
+ * invalidated on the standby. This situation may occur due to the following
+ * reasons:
+ * - The max_slot_wal_keep_size on the standby is insufficient to retain WAL
+ *   records from the restart_lsn of the slot.
+ * - primary_slot_name is temporarily reset to null and the physical slot is
+ *   removed.
+ * - The primary changes wal_level to a level lower than logical.
+ *
+ * The assumption is that these dropped local invalidated slots will get
+ * recreated in next sync-cycle and it is okay to drop and recreate such slots
+ * as long as these are not consumable on the standby (which is the case
+ * currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slot_list = NIL;
+	ListCell   *lc_slot;
+
+	/*
+	 * Get the list of local 'synced' slot so that those not on remote could
+	 * be dropped.
+	 */
+	local_slot_list = get_local_synced_slot_names();
+
+	foreach(lc_slot, local_slot_list)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc_slot);
+		bool		local_exists = false;
+		bool		locally_invalidated = false;
+
+		local_exists = check_sync_slot_validity(local_slot, remote_slot_list,
+												&locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!local_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Constructs the query in order to get failover logical slots
+ * information from the primary server.
+ */
+static void
+construct_slot_query(StringInfo s)
+{
+	/*
+	 * Fetch slots with failover enabled.
+	 *
+	 * If we are on cascading standby, we should fetch only those slots from
+	 * the first standby which have sync_state as either 'n' or 'r'. Slots
+	 * with sync_state as 'i' are not sync ready yet. And when we are on the
+	 * first standby, the primary server is supposed to have slots with
+	 * sync_state as 'n' only (or it may have all 'n', 'r' and 'i' if standby
+	 * is promoted as primary). Thus in all the cases, filter sync_state !='i'
+	 * is appropriate one.
+	 */
+	appendStringInfo(s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover and sync_state != 'i'");
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ */
+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+					 bool *slot_updated)
+{
+	ReplicationSlot *s;
+	char		sync_state = 0;
+
+	/*
+	 * Make sure that concerned WAL is received before syncing slot to target
+	 * lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* Search for the named slot */
+	if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		SpinLockAcquire(&s->mutex);
+		sync_state = s->data.sync_state;
+		SpinLockRelease(&s->mutex);
+	}
+
+	StartTransactionCommand();
+
+	/*
+	 * Already existing slot (created by slot sync worker) and ready for sync,
+	 * acquire and sync it.
+	 */
+	if (sync_state == SYNCSLOT_STATE_READY)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&MyReplicationSlot->mutex);
+			MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&MyReplicationSlot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * With hot_standby_feedback enabled and invalidations handled
+		 * apropriately as above, this should never happen.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn)
+		{
+			ereport(ERROR,
+					errmsg("not synchronizing local slot \"%s\" LSN(%X/%X)"
+						   " to remote slot's LSN(%X/%X) as synchronization "
+						   " would move it backwards", remote_slot->name,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   LSN_FORMAT_ARGS(remote_slot->restart_lsn)));
+
+			goto cleanup;
+		}
+
+		if (remote_slot->confirmed_lsn != MyReplicationSlot->data.confirmed_flush ||
+			remote_slot->restart_lsn != MyReplicationSlot->data.restart_lsn ||
+			remote_slot->catalog_xmin != MyReplicationSlot->data.catalog_xmin)
+		{
+			/* Update LSN of slot to remote slot's current position */
+			local_slot_update(remote_slot);
+			ReplicationSlotSave();
+			*slot_updated = true;
+		}
+	}
+
+	/*
+	 * Already existing slot but not ready (i.e. waiting for the primary
+	 * server to catch-up), lets attempt to make it sync-ready now.
+	 */
+	else if (sync_state == SYNCSLOT_STATE_INITIATED)
+	{
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&MyReplicationSlot->mutex);
+			MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&MyReplicationSlot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/*
+		 * Refer the slot creation part (last 'else' block) for more details
+		 * on this wait.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+			{
+				goto cleanup;
+			}
+		}
+
+		/*
+		 * Wait for primary is over, update the lsns and mark the slot as
+		 * READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Save the changes */
+		ReplicationSlotMarkDirty();
+		ReplicationSlotSave();
+
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready "
+							"now", remote_slot->name));
+	}
+	/* User created slot with the same name exists, raise ERROR. */
+	else if (sync_state == SYNCSLOT_STATE_NONE)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("skipping sync of slot \"%s\" as it is a user created"
+						" slot", remote_slot->name),
+				 errdetail("This slot has failover enabled on the primary and"
+						   " thus is sync candidate but user created slot with"
+						   " the same name already exists on the standby")));
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+		ReplicationSlot *slot;
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover,
+							  SYNCSLOT_STATE_INITIATED);
+
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * If the local restart_lsn and/or local catalog_xmin is ahead of
+		 * those on the remote then we cannot create the local slot in sync
+		 * with the primary server because that would mean moving the local
+		 * slot backwards and we might not have WALs retained for old LSN. In
+		 * this case we will wait for the primary server's restart_lsn and
+		 * catalog_xmin to catch up with the local one before attempting the
+		 * sync.
+		 */
+		if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  MyReplicationSlot->data.catalog_xmin))
+		{
+			bool		persist;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &persist))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid) and attempt the wait and synchronization in
+				 * the next cycle.
+				 */
+				if (persist)
+				{
+					ReplicationSlotPersist();
+					*slot_updated = true;
+				}
+
+				goto cleanup;
+			}
+		}
+
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&MyReplicationSlot->mutex);
+		MyReplicationSlot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&MyReplicationSlot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		*slot_updated = true;
+
+		ereport(LOG, errmsg("newly locally created slot \"%s\" is sync-ready "
+							"now", remote_slot->name));
+	}
+
+cleanup:
+
+	ReplicationSlotRelease();
+	CommitTransactionCommand();
+
+	return;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and update
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns nap time for the next sync-cycle.
+ */
+static long
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *slot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	MemoryContext oldctx = CurrentMemoryContext;
+	long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+	ListCell   *cell;
+	bool		slot_updated = false;
+	TimestampTz now;
+
+	/* The primary_slot_name is not set yet or WALs not received yet */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		return naptime;
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* The syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	/*
+	 * Make result tuples live outside TopTransactionContext to make them
+	 * accessible even after transaction is committed.
+	 */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct query to get slots info from the primary server */
+	initStringInfo(&s);
+	construct_slot_query(&s);
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(slot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(slot, 5,
+																	   &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(slot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(slot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(slot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(slot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(slot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(cell, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(cell);
+
+		synchronize_one_slot(wrconn, remote_slot, &slot_updated);
+	}
+
+	now = GetCurrentTimestamp();
+
+	/*
+	 * If any of the slots get updated in this sync-cycle, retain default
+	 * naptime and update 'last_update_time' in slot sync worker. But if no
+	 * activity is observed in this sync-cycle, then increase naptime provided
+	 * inactivity time reaches threshold.
+	 */
+	if (slot_updated)
+		last_update_time = now;
+
+	else if (TimestampDifferenceExceeds(last_update_time,
+										now, WORKER_INACTIVITY_THRESHOLD_MS))
+		naptime = WORKER_INACTIVITY_NAPTIME_MS;
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	return naptime;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker",
+							&err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Connects primary to validate the slot specified in primary_slot_name.
+ *
+ * Exits the worker if physical slot with the specified name does not exist.
+ */
+static void
+validate_primary_slot(WalReceiverConn *wrconn)
+{
+	WalRcvExecResult *res;
+	Oid			slotRow[1] = {BOOLOID};
+	StringInfoData cmd;
+	bool		isnull;
+	TupleTableSlot *slot;
+	bool		valid;
+	bool		tuple_ok;
+
+	/* Syscache access needs a transaction env. */
+	StartTransactionCommand();
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "select count(*) = 1 from pg_replication_slots where "
+					 "slot_type='physical' and slot_name=%s",
+					 quote_literal_cstr(PrimarySlotName));
+
+	res = walrcv_exec(wrconn, cmd.data, 1, slotRow);
+	pfree(cmd.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch primary_slot_name info from the "
+						"primary: %s", res->err)));
+
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	tuple_ok = tuplestore_gettupleslot(res->tuplestore, true, false, slot);
+	Assert(tuple_ok);			/* It must return one tuple */
+
+	valid = DatumGetBool(slot_getattr(slot, 1, &isnull));
+	Assert(!isnull);
+
+	if (!valid)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as slot specified in "
+					   "primary_slot_name is not valid"));
+
+	ExecClearTuple(slot);
+	walrcv_clear_result(res);
+	CommitTransactionCommand();
+}
+
+/*
+ * Checks if GUCs are set appropriately before starting slot sync worker
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+	/*
+	 * Since 'enable_syncslot' is ON, check that other GUC settings
+	 * (primary_slot_name, hot_standby_feedback, wal_level, primary_conninfo)
+	 * are compatible with slot synchronization. If not, raise ERROR.
+	 */
+
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as primary_slot_name is "
+					   "not set"));
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as hot_standby_feedback "
+					   "is off"));
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+				errmsg("exiting slots synchronisation as it requires "
+					   "wal_level >= logical"));
+
+	/*
+	 * The primary_conninfo is required to make connection to primary for
+	 * getting slots information.
+	 */
+	if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as primary_conninfo "
+					   "is not set"));
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	*dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (*dbname == NULL)
+		ereport(ERROR,
+				errmsg("exiting slots synchronization as dbname is not "
+					   "specified in primary_conninfo"));
+
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs changed, validate the values again
+ * through validate_slotsync_parameters() which will exit the worker
+ * if validaity fails.
+ */
+static void
+slotsync_reread_config(WalReceiverConn *wrconn)
+{
+	char	   *conninfo = pstrdup(PrimaryConnInfo);
+	char	   *slotname = pstrdup(PrimarySlotName);
+	bool		syncslot = enable_syncslot;
+	bool		standbyfeedback = hot_standby_feedback;
+	bool		revalidate = false;
+	char	   *dbname;
+	bool		conninfoChanged;
+	bool		slotnameChanged;
+
+	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	Assert(dbname);
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	conninfoChanged = strcmp(conninfo, PrimaryConnInfo) != 0;
+	slotnameChanged = strcmp(slotname, PrimarySlotName) != 0;
+
+	if (conninfoChanged || slotnameChanged ||
+		(syncslot != enable_syncslot) ||
+		(standbyfeedback != hot_standby_feedback))
+	{
+		revalidate = true;
+	}
+
+	pfree(conninfo);
+	pfree(slotname);
+
+	if (revalidate)
+	{
+		char	   *new_dbname;
+
+		validate_slotsync_parameters(&new_dbname);
+
+		/*
+		 * Since we have initialized this worker with old dbname, thus exit if
+		 * dbname changed. Let it get restarted and connect to new dbname
+		 * specified.
+		 */
+		if (conninfoChanged && strcmp(dbname, new_dbname) != 0)
+		{
+			ereport(ERROR,
+					errmsg("exiting slot sync woker as dbname in "
+						   "primary_conninfo changed"));
+		}
+
+		if (slotnameChanged)
+			validate_primary_slot(wrconn);
+	}
+}
+
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting"
+					   " down on receiving SIGINT"));
+
+		walrcv_disconnect(wrconn);
+		proc_exit(0);
+	}
+
+
+	if (ConfigReloadPending)
+		slotsync_reread_config(wrconn);
+}
+
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = 0;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	Assert(SlotSyncWorker->pid == 0);
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	validate_slotsync_parameters(&dbname);
+
+	/*
+	 * Connect to the database specified by user in primary_conninfo. We need
+	 * a database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/*
+	 * Connect to primary and validate the slot specified in
+	 * primary_slot_name.
+	 */
+	validate_primary_slot(wrconn);
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime;
+
+		ProcessSlotSyncInterrupts(wrconn);
+
+		naptime = synchronize_slots(wrconn);
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsSlotSyncWorker(void)
+{
+	return SlotSyncWorker->pid == MyProcPid;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (!SlotSyncWorker->pid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Wait for it to die. */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long. */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (!SlotSyncWorker->pid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Allocate and initialize slow sync worker shared memory
+ */
+void
+SlotSyncWorkerShmemInit(void)
+{
+	Size		size;
+	bool		found;
+
+	size = sizeof(SlotSyncWorkerCtx);
+	size = MAXALIGN(size);
+
+	SlotSyncWorker = (SlotSyncWorkerCtx *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
+
+	if (!found)
+	{
+		memset(SlotSyncWorker, 0, size);
+		SpinLockInit(&SlotSyncWorker->mutex);
+	}
+}
+
+/*
+ * Register the background worker for slots synchronization provided
+ * enable_syncslot is ON.
+ */
+void
+SlotSyncWorkerRegister(void)
+{
+	BackgroundWorker bgw;
+
+	if (!enable_syncslot)
+	{
+		ereport(LOG,
+				errmsg("skipping slots synchronization as enable_syncslot is "
+					   "disabled."));
+		return;
+	}
+
+	memset(&bgw, 0, sizeof(bgw));
+
+	/* We need database connection which needs shared-memory access as well. */
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+
+	/* Start as soon as a consistent state has been reached in a hot standby */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState_HotStandby;
+
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "replication slot sync worker");
+	snprintf(bgw.bgw_type, BGW_MAXLEN,
+			 "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
+	bgw.bgw_notify_pid = 0;
+	bgw.bgw_main_arg = (Datum) 0;
+
+	RegisterBackgroundWorker(&bgw);
+}
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index d2d8bf1a7a..f1675dbd85 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "commands/subscriptioncmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e46a1955e8..7b3784c212 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -141,7 +141,20 @@
  * subscribe to the new primary without losing any data.
  *
  * However, we do not enable failover for slots created by the table sync
- * worker.
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby due to the following reasons:
+ *
+ * - The standby needs to wait for the primary server to catch up because the
+ *   local restart_lsn of the newly created slot on the standby is set using
+ *   the latest redo position (GetXLogReplayRecPtr()), which is typically ahead
+ *   of the primary's restart_lsn.
+ * - The table sync slot's restart_lsn won't be advanced until the state
+ *   becomes SUBREL_STATE_CATCHUP.
+ *
+ * Therefore, if a failover happens before the restart_lsn advances, the table
+ * sync slot will not be synced to the standby. Consequently, we will not be
+ * able to subscribe to the promoted standby due to the absence of the
+ * necessary table sync slot.
  *
  * Additionally, failover is not enabled for the main slot if the table sync is
  * in progress. This is because if a failover occurs while the table sync
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index ca8b07a095..fe2503f37f 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -266,7 +267,7 @@ ReplicationSlotValidateName(const char *name, int elevel)
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover)
+					  bool two_phase, bool failover, char sync_state)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -327,6 +328,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = sync_state;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -686,12 +688,26 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+	{
+		ReplicationSlotRelease();
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary.")));
+	}
+
 	ReplicationSlotDropAcquired();
 }
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index c87f61666d..e5ba5956aa 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -44,7 +44,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false);
+						  false, SYNCSLOT_STATE_NONE);
 
 	if (immediately_reserve)
 	{
@@ -137,7 +137,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover);
+						  failover, SYNCSLOT_STATE_NONE);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index b5493fcd69..6b65c3c7dc 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1071,7 +1071,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false);
+							  false, false, SYNCSLOT_STATE_NONE);
 
 		if (reserve_wal)
 		{
@@ -1102,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover);
+							  two_phase, failover, SYNCSLOT_STATE_NONE);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, true);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2225a4a6e6..76a0f9be58 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -36,6 +36,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -336,6 +337,7 @@ CreateOrAttachShmemStructs(void)
 	WalRcvShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7298a187d1..92ea349488 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -60,6 +60,7 @@
 #include "replication/logicalworker.h"
 #include "replication/slot.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
@@ -3286,6 +3287,17 @@ ProcessInterrupts(void)
 			 */
 			proc_exit(1);
 		}
+		else if (IsSlotSyncWorker())
+		{
+			ereport(DEBUG1,
+					(errmsg_internal("replication slot sync worker is shutting down due to administrator command")));
+
+			/*
+			 * Slot sync worker can be stopped at any time.
+			 * Use exit status 1 so the background worker is restarted.
+			 */
+			proc_exit(1);
+		}
 		else if (IsBackgroundWorker)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 4b776266a4..2b40d5db6e 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2021,6 +2022,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_POSTMASTER, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5d940b72cd..39224137e1 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -358,6 +358,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = off			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d906734750..6a8192ad83 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 214dc6c29e..75b4b2040d 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -17,6 +17,7 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"
 
 extern ObjectAddress CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										bool isTopLevel);
@@ -28,4 +29,7 @@ extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
 extern char defGetStreamingMode(DefElem *def);
 
+extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn,
+										char *slotname, bool missing_ok);
+
 #endif							/* SUBSCRIPTIONCMDS_H */
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index e90ff376a6..8559900b70 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -79,6 +79,7 @@ typedef enum
 	BgWorkerStart_PostmasterStart,
 	BgWorkerStart_ConsistentState,
 	BgWorkerStart_RecoveryFinished,
+	BgWorkerStart_ConsistentState_HotStandby,
 } BgWorkerStartTime;
 
 #define BGW_DEFAULT_RESTART_INTERVAL	60
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 5ddad69348..5fa7a18111 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -15,7 +15,6 @@
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "storage/spin.h"
-#include "replication/walreceiver.h"
 
 /*
  * Behaviour of replication slots, upon release or crash.
@@ -52,6 +51,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +119,13 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Is this a slot created by a sync-slot worker?
+	 *
+	 * Relevant for logical slots on the physical standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)? Only
 	 * relevant for logical slots on the primary server.
@@ -225,9 +239,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover);
+								  bool two_phase, bool failover,
+								  char sync_state);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
@@ -253,7 +268,6 @@ extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_l
 extern int	ReplicationSlotIndex(ReplicationSlot *slot);
 extern bool ReplicationSlotName(int index, Name name);
 extern void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot);
-extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok);
 
 extern void StartupReplicationSlots(void);
 extern void CheckPointReplicationSlots(bool is_shutdown);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index f1135762fb..c96a814b26 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -19,6 +19,7 @@
 #include "pgtime.h"
 #include "port/atomics.h"
 #include "replication/logicalproto.h"
+#include "replication/slot.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/latch.h"
@@ -279,6 +280,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -403,6 +419,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -428,6 +445,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 84bb79ac0f..16b1129fcd 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -237,6 +237,11 @@ extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
+/* Slot sync worker objects */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+extern PGDLLIMPORT bool enable_syncslot;
+
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
@@ -326,6 +331,13 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
+extern void SlotSyncWorkerRegister(void);
+extern void ShutDownSlotSync(void);
+extern void slotsync_drop_initiated_slots(void);
+extern bool IsSlotSyncWorker(void);
+extern void SlotSyncWorkerShmemInit(void);
+
 #define isParallelApplyWorker(worker) ((worker)->in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
 #define isTablesyncWorker(worker) ((worker)->in_use && \
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
index 09b5d006a5..eb7667b1b3 100644
--- a/src/test/recovery/t/050_standby_failover_slots_sync.pl
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -295,4 +295,131 @@ is( $primary->safe_psql(
 
 $subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
 
+# Test logical failover slots on the standby
+# Configure standby3 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub1_slot->| ----> subscriber1 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb3_slot--->| ----> standby3 (connected via streaming replication)
+#                                        |                 lsub1_slot(synced_slot)
+
+# Cleanup old standby_slot_names
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = ''
+));
+$primary->start;
+
+$primary->psql('postgres',
+		q{SELECT pg_create_physical_replication_slot('sb3_slot');});
+
+$backup_name = 'backup2';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+my $connstr_1 = $primary->connstr;
+$standby3->stop;
+$standby3->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb3_slot'
+});
+$standby3->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+$standby3->start;
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb3_slot'
+));
+$primary->start;
+
+# Restart the standby
+$standby3->restart;
+
+# Wait for the standby to start sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots;}
+  ),
+  'lsub1_slot',
+  'failover slot was created');
+
+# Verify slot properties on the standby
+is( $standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|r",
+  'logical slot has sync_state as ready and failover as true on standby');
+
+# Verify slot properties on the primary
+is( $primary->safe_psql('postgres',
+    q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}
+  ),
+  "t|n",
+  'logical slot has sync_state as none and failover as true on primary');
+
+# Test to confirm that restart_lsn of the logical slot on the primary is synced to the standby
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_int;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# let the slots get synced on the standby
+sleep 2;
+
+# Get the restart_lsn for the logical slot lsub1_slot on the primary
+my $primary_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that restart_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'restart_lsn of slot lsub1_slot synced to standby');
+
+# Get the confirmed_flush_lsn for the logical slot lsub1_slot on the primary
+$primary_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that confirmed_flush_lsn of lsub1_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub1_slot synced to the standby');
+
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 175bcecfd1..9a79feaddf 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2317,6 +2317,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2575,6 +2576,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorkerCtx
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.30.0.windows.2



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 11:36                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-08 12:05                                             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-11 00:16                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-11 02:19                                                 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-12-11 07:32                                                   ` Peter Smith <[email protected]>
  2023-12-11 08:35                                                     ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-12 09:47                                                     ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  0 siblings, 2 replies; 113+ messages in thread

From: Peter Smith @ 2023-12-11 07:32 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; +Cc: Amit Kapila <[email protected]>; shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Here are some review comments for v44-0001

======
src/backend/replication/slot.c


1. ReplicationSlotCreate

  *     during getting changes, if the two_phase option is enabled it can skip
  *     prepare because by that time start decoding point has been moved. So the
  *     user will only get commit prepared.
+ * failover: Allows the slot to be synced to physical standbys so that logical
+ *     replication can be resumed after failover.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,

~

/Allows the slot.../If enabled, allows the slot.../

======

2. validate_standby_slots

+validate_standby_slots(char **newval)
+{
+ char    *rawname;
+ List    *elemlist;
+ ListCell   *lc;
+ bool ok = true;
+
+ /* Need a modifiable copy of string */
+ rawname = pstrdup(*newval);
+
+ /* Verify syntax and parse string into list of identifiers */
+ if (!(ok = SplitIdentifierString(rawname, ',', &elemlist)))
+ GUC_check_errdetail("List syntax is invalid.");
+
+ /*
+ * If there is a syntax error in the name or if the replication slots'
+ * data is not initialized yet (i.e., we are in the startup process), skip
+ * the slot verification.
+ */
+ if (!ok || !ReplicationSlotCtl)
+ {
+ pfree(rawname);
+ list_free(elemlist);
+ return ok;
+ }


2a.
You don't need to initialize 'ok' during declaration because it is
assigned immediately anyway.

~

2b.
AFAIK assignment within a conditional like this is not a normal PG
coding style unless there is no other way to do it.

~

2c.
/into list/into a list/

SUGGESTION
/* Verify syntax and parse string into a list of identifiers */
ok = SplitIdentifierString(rawname, ',', &elemlist);
if (!ok)
  GUC_check_errdetail("List syntax is invalid.");


~~~

3. assign_standby_slot_names

+ if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+ {
+ /* This should not happen if GUC checked check_standby_slot_names. */
+ elog(ERROR, "list syntax is invalid");
+ }

This error here and in validate_standby_slots() are different --
"list" versus "List".

======
src/backend/replication/walsender.c


4. WalSndFilterStandbySlots


+ foreach(lc, standby_slots_cpy)
+ {
+ char    *name = lfirst(lc);
+ XLogRecPtr restart_lsn = InvalidXLogRecPtr;
+ bool invalidated = false;
+ char    *warningfmt = NULL;
+ ReplicationSlot *slot;
+
+ slot = SearchNamedReplicationSlot(name, true);
+
+ if (slot && SlotIsPhysical(slot))
+ {
+ SpinLockAcquire(&slot->mutex);
+ restart_lsn = slot->data.restart_lsn;
+ invalidated = slot->data.invalidated != RS_INVAL_NONE;
+ SpinLockRelease(&slot->mutex);
+ }
+
+ /* Continue if the current slot hasn't caught up. */
+ if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
+ restart_lsn < wait_for_lsn)
+ {
+ /* Log warning if no active_pid for this physical slot */
+ if (slot->active_pid == 0)
+ ereport(WARNING,
+ errmsg("replication slot \"%s\" specified in parameter \"%s\" does
not have active_pid",
+    name, "standby_slot_names"),
+ errdetail("Logical replication is waiting on the "
+   "standby associated with \"%s\"", name),
+ errhint("Consider starting standby associated with "
+ "\"%s\" or amend standby_slot_names", name));
+
+ continue;
+ }
+ else if (!slot)
+ {
+ /*
+ * It may happen that the slot specified in standby_slot_names GUC
+ * value is dropped, so let's skip over it.
+ */
+ warningfmt = _("replication slot \"%s\" specified in parameter
\"%s\" does not exist, ignoring");
+ }
+ else if (SlotIsLogical(slot))
+ {
+ /*
+ * If a logical slot name is provided in standby_slot_names, issue
+ * a WARNING and skip it. Although logical slots are disallowed in
+ * the GUC check_hook(validate_standby_slots), it is still
+ * possible for a user to drop an existing physical slot and
+ * recreate a logical slot with the same name. Since it is
+ * harmless, a WARNING should be enough, no need to error-out.
+ */
+ warningfmt = _("cannot have logical replication slot \"%s\" in
parameter \"%s\", ignoring");
+ }
+ else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
+ {
+ /*
+ * Specified physical slot may have been invalidated, so there is no point
+ * in waiting for it.
+ */
+ warningfmt = _("physical slot \"%s\" specified in parameter \"%s\"
has been invalidated, ignoring");
+ }
+ else
+ {
+ Assert(restart_lsn >= wait_for_lsn);
+ }

This if/else chain seems structured awkwardly. IMO it would be tidier
to eliminate the NULL slot and IsLogicalSlot up-front, which would
also simplify some of the subsequent conditions

SUGGESTION

slot = SearchNamedReplicationSlot(name, true);

if (!slot)
{
...
}
else if (SlotIsLogical(slot))
{
...
}
else
{
  Assert(SlotIsPhysical(slot))

  SpinLockAcquire(&slot->mutex);
  restart_lsn = slot->data.restart_lsn;
  invalidated = slot->data.invalidated != RS_INVAL_NONE;
  SpinLockRelease(&slot->mutex);

  if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
  {
  ...
  }
  else if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
restart_lsn < wait_for_lsn)
  {
  ...
  }
  else
  {
    Assert(restart_lsn >= wait_for_lsn);
  }
}

~~~~

5. WalSndWaitForWal

+ else
+ {
+ /* already caught up and doesn't need to wait for standby_slots */
  break;
+ }

/Already/already/

======
src/test/recovery/t/050_standby_failover_slots_sync.pl


6.
+$subscriber1->safe_psql('postgres',
+ "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true
+$subscriber1->safe_psql('postgres',
+     "CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' "
+   . "PUBLICATION regress_mypub WITH (slot_name = lsub1_slot,
failover = true);"
+);


Consider combining these DDL statements.

~~~

7.
+$subscriber2->safe_psql('postgres',
+ "CREATE TABLE tab_int (a int PRIMARY KEY);");
+$subscriber2->safe_psql('postgres',
+     "CREATE SUBSCRIPTION regress_mysub2 CONNECTION '$publisher_connstr' "
+   . "PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);");

Consider combining these DDL statements

~~~

8.
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby slot's
+# restart_lsn is advanced or the slots is removed from the standby_slot_names
+# list
+$publisher->safe_psql('postgres', "TRUNCATE tab_int;");
+$publisher->wait_for_catchup('regress_mysub1');
+$standby1->stop;

/with specified/with the specified/

/or the slots is/or the slot is/

~~~

9.
+# Create some data on primary

/on primary/on the primary/

~~~

10.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 10 FROM tab_int;");
+is($result, 't',
+ "subscriber1 doesn't get data as the sb1_slot doesn't catch up");


I felt instead of checking for 10 maybe it's more consistent with the
previous code to assign again that $primary_row_count variable to 20;

Then check that those primary rows are not all yet received like:

SELECT count(*) < $primary_row_count FROM tab_int;

~~~

11.
+# Now that the standby lsn has advanced, primary must send the decoded
+# changes to the subscription.
+$publisher->wait_for_catchup('regress_mysub1');
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 20 FROM tab_int;");
+is($result, 't',
+ "subscriber1 gets data from primary after standby1 is removed from
the standby_slot_names list"
+);

/primary must/the primary must/

(continuing the suggestion from the previous review comment)

Now this SQL can use the variable too:

subscriber1->safe_psql('postgres', "SELECT count(*) =
$primary_row_count FROM tab_int;");

~~~

12.
+
+# Create another subscription enabling failover
+$subscriber1->safe_psql('postgres',
+     "CREATE SUBSCRIPTION regress_mysub3 CONNECTION '$publisher_connstr' "
+   . "PUBLICATION regress_mypub WITH (slot_name = lsub3_slot,
copy_data=false, failover = true, create_slot = false);"
+);


Maybe give some more information in that comment:

SUGGESTION
Create another subscription (using the same slot created above) that
enables failover.

======
Kind Regards,
Peter Smith.
Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 11:36                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-08 12:05                                             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-11 00:16                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-11 02:19                                                 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-11 07:32                                                   ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
@ 2023-12-11 08:35                                                     ` Amit Kapila <[email protected]>
  1 sibling, 0 replies; 113+ messages in thread

From: Amit Kapila @ 2023-12-11 08:35 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; +Cc: Zhijie Hou (Fujitsu) <[email protected]>; shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Mon, Dec 11, 2023 at 1:02 PM Peter Smith <[email protected]> wrote:
>
> Here are some review comments for v44-0001
>
> ~~~
>
> 3. assign_standby_slot_names
>
> + if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
> + {
> + /* This should not happen if GUC checked check_standby_slot_names. */
> + elog(ERROR, "list syntax is invalid");
> + }
>
> This error here and in validate_standby_slots() are different --
> "list" versus "List".
>

Note here elog(ERROR,.. is used and in the other place it is part of
the detail message. I have suggested in my previous review to make
them the same but I overlooked the difference, so I think we should
change the message to "invalid list syntax" as it was there
previously.

> ======
> src/backend/replication/walsender.c
>
>
> 4. WalSndFilterStandbySlots
>
>
> + foreach(lc, standby_slots_cpy)
> + {
> + char    *name = lfirst(lc);
> + XLogRecPtr restart_lsn = InvalidXLogRecPtr;
> + bool invalidated = false;
> + char    *warningfmt = NULL;
> + ReplicationSlot *slot;
> +
> + slot = SearchNamedReplicationSlot(name, true);
> +
> + if (slot && SlotIsPhysical(slot))
> + {
> + SpinLockAcquire(&slot->mutex);
> + restart_lsn = slot->data.restart_lsn;
> + invalidated = slot->data.invalidated != RS_INVAL_NONE;
> + SpinLockRelease(&slot->mutex);
> + }
> +
> + /* Continue if the current slot hasn't caught up. */
> + if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
> + restart_lsn < wait_for_lsn)
> + {
> + /* Log warning if no active_pid for this physical slot */
> + if (slot->active_pid == 0)
> + ereport(WARNING,
> + errmsg("replication slot \"%s\" specified in parameter \"%s\" does
> not have active_pid",
> +    name, "standby_slot_names"),
> + errdetail("Logical replication is waiting on the "
> +   "standby associated with \"%s\"", name),
> + errhint("Consider starting standby associated with "
> + "\"%s\" or amend standby_slot_names", name));
> +
> + continue;
> + }
> + else if (!slot)
> + {
> + /*
> + * It may happen that the slot specified in standby_slot_names GUC
> + * value is dropped, so let's skip over it.
> + */
> + warningfmt = _("replication slot \"%s\" specified in parameter
> \"%s\" does not exist, ignoring");
> + }
> + else if (SlotIsLogical(slot))
> + {
> + /*
> + * If a logical slot name is provided in standby_slot_names, issue
> + * a WARNING and skip it. Although logical slots are disallowed in
> + * the GUC check_hook(validate_standby_slots), it is still
> + * possible for a user to drop an existing physical slot and
> + * recreate a logical slot with the same name. Since it is
> + * harmless, a WARNING should be enough, no need to error-out.
> + */
> + warningfmt = _("cannot have logical replication slot \"%s\" in
> parameter \"%s\", ignoring");
> + }
> + else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
> + {
> + /*
> + * Specified physical slot may have been invalidated, so there is no point
> + * in waiting for it.
> + */
> + warningfmt = _("physical slot \"%s\" specified in parameter \"%s\"
> has been invalidated, ignoring");
> + }
> + else
> + {
> + Assert(restart_lsn >= wait_for_lsn);
> + }
>
> This if/else chain seems structured awkwardly. IMO it would be tidier
> to eliminate the NULL slot and IsLogicalSlot up-front, which would
> also simplify some of the subsequent conditions
>
> SUGGESTION
>
> slot = SearchNamedReplicationSlot(name, true);
>
> if (!slot)
> {
> ...
> }
> else if (SlotIsLogical(slot))
> {
> ...
> }
> else
> {
>   Assert(SlotIsPhysical(slot))
>
>   SpinLockAcquire(&slot->mutex);
>   restart_lsn = slot->data.restart_lsn;
>   invalidated = slot->data.invalidated != RS_INVAL_NONE;
>   SpinLockRelease(&slot->mutex);
>
>   if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
>   {
>   ...
>   }
>   else if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
> restart_lsn < wait_for_lsn)
>   {
>   ...
>   }
>   else
>   {
>     Assert(restart_lsn >= wait_for_lsn);
>   }
> }
>

+1.

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 11:36                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-08 12:05                                             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-11 00:16                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-11 02:19                                                 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-11 07:32                                                   ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
@ 2023-12-12 09:47                                                     ` Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-12 12:26                                                       ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-12-12 09:47 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; +Cc: Amit Kapila <[email protected]>; shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Monday, December 11, 2023 3:32 PM Peter Smith <[email protected]>
> 
> Here are some review comments for v44-0001
> 
> ======
> src/backend/replication/slot.c
> 
> 
> 1. ReplicationSlotCreate
> 
>   *     during getting changes, if the two_phase option is enabled it can skip
>   *     prepare because by that time start decoding point has been moved. So
> the
>   *     user will only get commit prepared.
> + * failover: Allows the slot to be synced to physical standbys so that logical
> + *     replication can be resumed after failover.
>   */
>  void
>  ReplicationSlotCreate(const char *name, bool db_specific,
> 
> ~
> 
> /Allows the slot.../If enabled, allows the slot.../

Changed.

> 
> ======
> 
> 2. validate_standby_slots
> 
> +validate_standby_slots(char **newval)
> +{
> + char    *rawname;
> + List    *elemlist;
> + ListCell   *lc;
> + bool ok = true;
> +
> + /* Need a modifiable copy of string */ rawname = pstrdup(*newval);
> +
> + /* Verify syntax and parse string into list of identifiers */ if (!(ok
> + = SplitIdentifierString(rawname, ',', &elemlist)))
> + GUC_check_errdetail("List syntax is invalid.");
> +
> + /*
> + * If there is a syntax error in the name or if the replication slots'
> + * data is not initialized yet (i.e., we are in the startup process),
> + skip
> + * the slot verification.
> + */
> + if (!ok || !ReplicationSlotCtl)
> + {
> + pfree(rawname);
> + list_free(elemlist);
> + return ok;
> + }
> 
> 
> 2a.
> You don't need to initialize 'ok' during declaration because it is assigned
> immediately anyway.
> 
> ~
> 
> 2b.
> AFAIK assignment within a conditional like this is not a normal PG coding style
> unless there is no other way to do it.
> 

Changed.

> ~
> 
> 2c.
> /into list/into a list/
> 
> SUGGESTION
> /* Verify syntax and parse string into a list of identifiers */ ok =
> SplitIdentifierString(rawname, ',', &elemlist); if (!ok)
>   GUC_check_errdetail("List syntax is invalid.");
> 
> 

Changed.

> ~~~
> 
> 3. assign_standby_slot_names
> 
> + if (!SplitIdentifierString(standby_slot_names_cpy, ',',
> + &standby_slots)) {
> + /* This should not happen if GUC checked check_standby_slot_names. */
> + elog(ERROR, "list syntax is invalid"); }
> 
> This error here and in validate_standby_slots() are different -- "list" versus
> "List".
> 

The message has been changed to "invalid list syntax" to be consistent with other elog.

> ======
> src/backend/replication/walsender.c
> 
> 
> 4. WalSndFilterStandbySlots
> 
> 
> + foreach(lc, standby_slots_cpy)
> + {
> + char    *name = lfirst(lc);
> + XLogRecPtr restart_lsn = InvalidXLogRecPtr; bool invalidated = false;
> + char    *warningfmt = NULL;
> + ReplicationSlot *slot;
> +
> + slot = SearchNamedReplicationSlot(name, true);
> +
> + if (slot && SlotIsPhysical(slot))
> + {
> + SpinLockAcquire(&slot->mutex);
> + restart_lsn = slot->data.restart_lsn;
> + invalidated = slot->data.invalidated != RS_INVAL_NONE;
> + SpinLockRelease(&slot->mutex); }
> +
> + /* Continue if the current slot hasn't caught up. */ if (!invalidated
> + && !XLogRecPtrIsInvalid(restart_lsn) && restart_lsn < wait_for_lsn) {
> + /* Log warning if no active_pid for this physical slot */ if
> + (slot->active_pid == 0) ereport(WARNING, errmsg("replication slot
> + \"%s\" specified in parameter \"%s\" does
> not have active_pid",
> +    name, "standby_slot_names"),
> + errdetail("Logical replication is waiting on the "
> +   "standby associated with \"%s\"", name), errhint("Consider starting
> + standby associated with "
> + "\"%s\" or amend standby_slot_names", name));
> +
> + continue;
> + }
> + else if (!slot)
> + {
> + /*
> + * It may happen that the slot specified in standby_slot_names GUC
> + * value is dropped, so let's skip over it.
> + */
> + warningfmt = _("replication slot \"%s\" specified in parameter
> \"%s\" does not exist, ignoring");
> + }
> + else if (SlotIsLogical(slot))
> + {
> + /*
> + * If a logical slot name is provided in standby_slot_names, issue
> + * a WARNING and skip it. Although logical slots are disallowed in
> + * the GUC check_hook(validate_standby_slots), it is still
> + * possible for a user to drop an existing physical slot and
> + * recreate a logical slot with the same name. Since it is
> + * harmless, a WARNING should be enough, no need to error-out.
> + */
> + warningfmt = _("cannot have logical replication slot \"%s\" in
> parameter \"%s\", ignoring");
> + }
> + else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated) {
> + /*
> + * Specified physical slot may have been invalidated, so there is no
> + point
> + * in waiting for it.
> + */
> + warningfmt = _("physical slot \"%s\" specified in parameter \"%s\"
> has been invalidated, ignoring");
> + }
> + else
> + {
> + Assert(restart_lsn >= wait_for_lsn);
> + }
> 
> This if/else chain seems structured awkwardly. IMO it would be tidier to
> eliminate the NULL slot and IsLogicalSlot up-front, which would also simplify
> some of the subsequent conditions
> 
> SUGGESTION
> 
> slot = SearchNamedReplicationSlot(name, true);
> 
> if (!slot)
> {
> ...
> }
> else if (SlotIsLogical(slot))
> {
> ...
> }
> else
> {
>   Assert(SlotIsPhysical(slot))
> 
>   SpinLockAcquire(&slot->mutex);
>   restart_lsn = slot->data.restart_lsn;
>   invalidated = slot->data.invalidated != RS_INVAL_NONE;
>   SpinLockRelease(&slot->mutex);
> 
>   if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
>   {
>   ...
>   }
>   else if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) && restart_lsn <
> wait_for_lsn)
>   {
>   ...
>   }
>   else
>   {
>     Assert(restart_lsn >= wait_for_lsn);
>   }
> }
> 

Changed.

> ~~~~
> 
> 5. WalSndWaitForWal
> 
> + else
> + {
> + /* already caught up and doesn't need to wait for standby_slots */
>   break;
> + }
> 
> /Already/already/
> 

Changed.

> ======
> src/test/recovery/t/050_standby_failover_slots_sync.pl
> 
> 
> 6.
> +$subscriber1->safe_psql('postgres',
> + "CREATE TABLE tab_int (a int PRIMARY KEY);");
> +
> +# Create a subscription with failover = true
> +$subscriber1->safe_psql('postgres',
> +     "CREATE SUBSCRIPTION regress_mysub1 CONNECTION
> '$publisher_connstr' "
> +   . "PUBLICATION regress_mypub WITH (slot_name = lsub1_slot,
> failover = true);"
> +);
> 
> 
> Consider combining these DDL statements.
> 

Changed.

> ~~~
> 
> 7.
> +$subscriber2->safe_psql('postgres',
> + "CREATE TABLE tab_int (a int PRIMARY KEY);");
> +$subscriber2->safe_psql('postgres',
> +     "CREATE SUBSCRIPTION regress_mysub2 CONNECTION
> '$publisher_connstr' "
> +   . "PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);");
> 
> Consider combining these DDL statements
> 

Changed.

> ~~~
> 
> 8.
> +# Stop the standby associated with specified physical replication slot
> +so that # the logical replication slot won't receive changes until the
> +standby slot's # restart_lsn is advanced or the slots is removed from
> +the standby_slot_names # list $publisher->safe_psql('postgres',
> +"TRUNCATE tab_int;"); $publisher->wait_for_catchup('regress_mysub1');
> +$standby1->stop;
> 
> /with specified/with the specified/
> 
> /or the slots is/or the slot is/
> 

Changed.

> ~~~
> 
> 9.
> +# Create some data on primary
> 
> /on primary/on the primary/
> 

Changed.

> ~~~
> 
> 10.
> +$result =
> +  $subscriber1->safe_psql('postgres', "SELECT count(*) = 10 FROM
> +tab_int;"); is($result, 't',
> + "subscriber1 doesn't get data as the sb1_slot doesn't catch up");
> 
> 
> I felt instead of checking for 10 maybe it's more consistent with the previous
> code to assign again that $primary_row_count variable to 20;
> 
> Then check that those primary rows are not all yet received like:
> 
> SELECT count(*) < $primary_row_count FROM tab_int;
> 

I think we'd better check the accurate number here to make sure the number is what we expect.

> ~~~
> 
> 11.
> +# Now that the standby lsn has advanced, primary must send the decoded
> +# changes to the subscription.
> +$publisher->wait_for_catchup('regress_mysub1');
> +$result =
> +  $subscriber1->safe_psql('postgres', "SELECT count(*) = 20 FROM
> +tab_int;"); is($result, 't',
> + "subscriber1 gets data from primary after standby1 is removed from
> the standby_slot_names list"
> +);
> 
> /primary must/the primary must/
> 
> (continuing the suggestion from the previous review comment)
> 
> Now this SQL can use the variable too:
> 
> subscriber1->safe_psql('postgres', "SELECT count(*) =
> $primary_row_count FROM tab_int;");
> 

Changed.

> ~~~
> 
> 12.
> +
> +# Create another subscription enabling failover
> +$subscriber1->safe_psql('postgres',
> +     "CREATE SUBSCRIPTION regress_mysub3 CONNECTION
> '$publisher_connstr' "
> +   . "PUBLICATION regress_mypub WITH (slot_name = lsub3_slot,
> copy_data=false, failover = true, create_slot = false);"
> +);
> 
> 
> Maybe give some more information in that comment:
> 
> SUGGESTION
> Create another subscription (using the same slot created above) that enables
> failover.
> 

Added.

Best Regards,
Hou zj


^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 11:36                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-08 12:05                                             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-11 00:16                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-11 02:19                                                 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-11 07:32                                                   ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-12 09:47                                                     ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-12-12 12:26                                                       ` Nisha Moond <[email protected]>
  2023-12-13 06:09                                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Nisha Moond @ 2023-12-12 12:26 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; +Cc: Peter Smith <[email protected]>; Amit Kapila <[email protected]>; shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

A review on v45 patch:

If one creates a logical slot with failover=true as -
select pg_create_logical_replication_slot('logical_slot','pgoutput',
false, true, true);

Then, uses the existing logical slot while creating a subscription -
postgres=#  create subscription sub4 connection 'dbname=postgres
host=localhost port=5433' publication pub1t4 WITH
(slot_name=logical_slot, create_slot=false, failover=true);
NOTICE:  changed the failover state of replication slot "logical_slot"
on publisher to false
CREATE SUBSCRIPTION

Despite configuring logical_slot's failover to true and specifying
failover=true during subscription creation, the NOTICE indicates a
change in the failover state to 'false', without providing any
explanation for this transition.
It can be confusing for users, so IMO, the notice should include the
reason for switching failover to 'false' or should give a hint to use
either refresh=false or copy_data=false to enable failover=true for
the slot as we do in other similar 'alter subscription...' scenarios.

--
Thanks & Regards,
Nisha






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 11:36                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-08 12:05                                             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-11 00:16                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-11 02:19                                                 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-11 07:32                                                   ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-12 09:47                                                     ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-12 12:26                                                       ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
@ 2023-12-13 06:09                                                         ` shveta malik <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: shveta malik @ 2023-12-13 06:09 UTC (permalink / raw)
  To: Nisha Moond <[email protected]>; +Cc: Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Tue, Dec 12, 2023 at 5:56 PM Nisha Moond <[email protected]> wrote:
>
> A review on v45 patch:
>
> If one creates a logical slot with failover=true as -
> select pg_create_logical_replication_slot('logical_slot','pgoutput',
> false, true, true);
>
> Then, uses the existing logical slot while creating a subscription -
> postgres=#  create subscription sub4 connection 'dbname=postgres
> host=localhost port=5433' publication pub1t4 WITH
> (slot_name=logical_slot, create_slot=false, failover=true);
> NOTICE:  changed the failover state of replication slot "logical_slot"
> on publisher to false
> CREATE SUBSCRIPTION
>
> Despite configuring logical_slot's failover to true and specifying
> failover=true during subscription creation, the NOTICE indicates a
> change in the failover state to 'false', without providing any
> explanation for this transition.
> It can be confusing for users, so IMO, the notice should include the
> reason for switching failover to 'false' or should give a hint to use
> either refresh=false or copy_data=false to enable failover=true for
> the slot as we do in other similar 'alter subscription...' scenarios.
>

Agree. The NOTICE should be more informative.

thanks
SHveta






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-07 22:58                                           ` Peter Smith <[email protected]>
  5 siblings, 0 replies; 113+ messages in thread

From: Peter Smith @ 2023-12-07 22:58 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Amit Kapila <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi.

Here is another review comment for the patch v43-0001.

======
src/bin/pg_dump/pg_dump.c

1. getSubscriptions

+ if (fout->remoteVersion >= 170000)
+ appendPQExpBufferStr(query,
+ " subfailoverstate\n");
+ else
+ appendPQExpBuffer(query,
+   " '%c' AS subfailoverstate\n",
+   LOGICALREP_FAILOVER_STATE_DISABLED);
+

That first appended string should include the table alias same as all
the nearby code does.

e.g. " subfailoverstate\n" should be " s.subfailoverstate\n"

======
Kind Regards,
Peter Smith.
Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-08 09:06                                           ` Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 08:17                                             ` Re: Synchronizing slots from primary to standby Dilip Kumar <[email protected]>
  5 siblings, 2 replies; 113+ messages in thread

From: Amit Kapila @ 2023-12-08 09:06 UTC (permalink / raw)
  To: shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; +Cc: Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Wed, Dec 6, 2023 at 4:53 PM shveta malik <[email protected]> wrote:
>
> PFA v43, changes are:
>

I wanted to discuss 0003 patch about cascading standby's. It is not
clear to me whether we want to allow physical standbys to further wait
for cascading standby to sync their slots. If we allow such a feature
one may expect even primary to wait for all the cascading standby's
because otherwise still logical subscriber can be ahead of one of the
cascading standby. I feel even if we want to allow such a behaviour we
can do it later once the main feature is committed. I think it would
be good to just allow logical walsenders on primary to wait for
physical standbys represented by GUC 'standby_slot_names'. If we agree
on that then it would be good to prohibit setting this GUC on standby
or at least it should be a no-op even if this GUC should be set on
physical standby.

Thoughts?

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-12-11 07:52                                             ` Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-12 09:46                                               ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  1 sibling, 2 replies; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-12-11 07:52 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; shveta malik <[email protected]>; +Cc: Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 12/8/23 10:06 AM, Amit Kapila wrote:
> On Wed, Dec 6, 2023 at 4:53 PM shveta malik <[email protected]> wrote:
>>
>> PFA v43, changes are:
>>
> 
> I wanted to discuss 0003 patch about cascading standby's. It is not
> clear to me whether we want to allow physical standbys to further wait
> for cascading standby to sync their slots. If we allow such a feature
> one may expect even primary to wait for all the cascading standby's
> because otherwise still logical subscriber can be ahead of one of the
> cascading standby.

I've the same feeling here. I think it would probably be expected that
the primary also wait for all the cascading standby.

> I feel even if we want to allow such a behaviour we
> can do it later once the main feature is committed. 

Agree.

> I think it would
> be good to just allow logical walsenders on primary to wait for
> physical standbys represented by GUC 'standby_slot_names'.

That makes sense for me for v1.

> If we agree
> on that then it would be good to prohibit setting this GUC on standby
> or at least it should be a no-op even if this GUC should be set on
> physical standby.

I'd prefer to completely prohibit it on standby (to make it very clear it's not
working at all) as long as one can enable it without downtime once the standby
is promoted (which is the case currently).

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-12-11 11:43                                               ` shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-11 11:43 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Amit Kapila <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Mon, Dec 11, 2023 at 1:22 PM Drouvot, Bertrand
<[email protected]> wrote:
>
> Hi,
>
> On 12/8/23 10:06 AM, Amit Kapila wrote:
> > On Wed, Dec 6, 2023 at 4:53 PM shveta malik <[email protected]> wrote:
> >>
> >> PFA v43, changes are:
> >>
> >
> > I wanted to discuss 0003 patch about cascading standby's. It is not
> > clear to me whether we want to allow physical standbys to further wait
> > for cascading standby to sync their slots. If we allow such a feature
> > one may expect even primary to wait for all the cascading standby's
> > because otherwise still logical subscriber can be ahead of one of the
> > cascading standby.
>
> I've the same feeling here. I think it would probably be expected that
> the primary also wait for all the cascading standby.
>
> > I feel even if we want to allow such a behaviour we
> > can do it later once the main feature is committed.
>
> Agree.
>
> > I think it would
> > be good to just allow logical walsenders on primary to wait for
> > physical standbys represented by GUC 'standby_slot_names'.
>
> That makes sense for me for v1.
>
> > If we agree
> > on that then it would be good to prohibit setting this GUC on standby
> > or at least it should be a no-op even if this GUC should be set on
> > physical standby.
>
> I'd prefer to completely prohibit it on standby (to make it very clear it's not
> working at all) as long as one can enable it without downtime once the standby
> is promoted (which is the case currently).

And I think slot-sync worker should exit as well on cascading standby. Thoughts?

If we agree on the above, then we need to look for a way to
distinguish between first and cascading standby. I could not find any
existing way to do so. One possible approach is to connect to the
remote using PrimaryConninfo and run 'pg_is_in_recovery()' there, if
it returns true, then it means we are cascading standby.  Any simpler
way to achieve this?

thanks
Shveta






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-13 05:10                                                 ` Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Amit Kapila @ 2023-12-13 05:10 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Mon, Dec 11, 2023 at 5:13 PM shveta malik <[email protected]> wrote:
>
> On Mon, Dec 11, 2023 at 1:22 PM Drouvot, Bertrand
> <[email protected]> wrote:
> >
> > > If we agree
> > > on that then it would be good to prohibit setting this GUC on standby
> > > or at least it should be a no-op even if this GUC should be set on
> > > physical standby.
> >
> > I'd prefer to completely prohibit it on standby (to make it very clear it's not
> > working at all) as long as one can enable it without downtime once the standby
> > is promoted (which is the case currently).
>
> And I think slot-sync worker should exit as well on cascading standby. Thoughts?
>

I think one has set all the valid parameters for the slot-sync worker
on standby, we should not exit, rather it should be no-op which means
it should not try to sync slots from another standby. One scenario
where this may help is when users promote the standby which has
already synced slots from the primary. In this case, cascading standby
will become non-cascading and should sync slots.

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-12-13 06:12                                                   ` shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-13 06:12 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Wed, Dec 13, 2023 at 10:40 AM Amit Kapila <[email protected]> wrote:
>
> On Mon, Dec 11, 2023 at 5:13 PM shveta malik <[email protected]> wrote:
> >
> > On Mon, Dec 11, 2023 at 1:22 PM Drouvot, Bertrand
> > <[email protected]> wrote:
> > >
> > > > If we agree
> > > > on that then it would be good to prohibit setting this GUC on standby
> > > > or at least it should be a no-op even if this GUC should be set on
> > > > physical standby.
> > >
> > > I'd prefer to completely prohibit it on standby (to make it very clear it's not
> > > working at all) as long as one can enable it without downtime once the standby
> > > is promoted (which is the case currently).
> >
> > And I think slot-sync worker should exit as well on cascading standby. Thoughts?
> >
>
> I think one has set all the valid parameters for the slot-sync worker
> on standby, we should not exit, rather it should be no-op which means
> it should not try to sync slots from another standby. One scenario
> where this may help is when users promote the standby which has
> already synced slots from the primary. In this case, cascading standby
> will become non-cascading and should sync slots.
>

Right, then perhaps we should increase naptime in this no-op case. It
could be even more then current inactivity naptime which is just
10sec. Shall it be say 5min in this case?

thanks
Shveta






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-13 11:37                                                     ` shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 04:45                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  0 siblings, 2 replies; 113+ messages in thread

From: shveta malik @ 2023-12-13 11:37 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Wed, Dec 13, 2023 at 11:42 AM shveta malik <[email protected]> wrote:
>
> On Wed, Dec 13, 2023 at 10:40 AM Amit Kapila <[email protected]> wrote:
> >
> > On Mon, Dec 11, 2023 at 5:13 PM shveta malik <[email protected]> wrote:
> > >
> > > On Mon, Dec 11, 2023 at 1:22 PM Drouvot, Bertrand
> > > <[email protected]> wrote:
> > > >
> > > > > If we agree
> > > > > on that then it would be good to prohibit setting this GUC on standby
> > > > > or at least it should be a no-op even if this GUC should be set on
> > > > > physical standby.
> > > >
> > > > I'd prefer to completely prohibit it on standby (to make it very clear it's not
> > > > working at all) as long as one can enable it without downtime once the standby
> > > > is promoted (which is the case currently).
> > >
> > > And I think slot-sync worker should exit as well on cascading standby. Thoughts?
> > >
> >
> > I think one has set all the valid parameters for the slot-sync worker
> > on standby, we should not exit, rather it should be no-op which means
> > it should not try to sync slots from another standby. One scenario
> > where this may help is when users promote the standby which has
> > already synced slots from the primary. In this case, cascading standby
> > will become non-cascading and should sync slots.
> >
>
> Right, then perhaps we should increase naptime in this no-op case. It
> could be even more then current inactivity naptime which is just
> 10sec. Shall it be say 5min in this case?
>

PFA v47 attached, changes are:

patch 001:
1) Addressed comment in [1]. Thanks Hou-san for this change.

patch 002
2) Slot sync worker will be no-op if it is on cascading standby as
suggested in [2]
3) StartTransaction related optimization as suggested in [3]
4) Few other comments' improvement and code-cleanup.

TODO:
--Few pending comments as I stated in [4] (mainly header inclusion in
tablesync.c, and 'r' to 'n' conversion on promotion)
--The comments given today in [5]

[1]: https://www.postgresql.org/message-id/CABdArM4Cow6aOLjGG9qnp6mhg%2B%2BgjK%3DHDO%3DKSU%3D6%3DyT7hLkkn...
[2]: https://www.postgresql.org/message-id/CAA4eK1Ki1O65SyA6ijh-Mq4zpzeh644fCmkrZXMJcQXHNrAw0Q%40mail.gma...
[3]: https://www.postgresql.org/message-id/CAA4eK1L3DiKL_Wq-VdU%2B9wmjmO5%2Bfrf%3DZHK9Lzq-7zOezPP%2BWg%40...
[4]: https://www.postgresql.org/message-id/CAJpy0uDcOf5Hvk_CdCCAbfx9SY%2Bog%3D%3D%3DtgiuhWKzkYyqebui9g%40...
[5]: https://www.postgresql.org/message-id/CAHut%2BPtOc7J_n24HJ6f_dFWTuD3X2ApOByQzZf6jZz%2B0wb-ebQ%40mail...

thanks
Shveta


Attachments:

  [application/octet-stream] v47-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (146.6K, ../../CAJpy0uBgGRJvWYfJD=3XE8OZ0V=X1keOYXxXEkpJqYk00BAzZg@mail.gmail.com/2-v47-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From 92a8dd0a8f982251ff484281c3e92fa60f6a544b Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Wed, 6 Dec 2023 15:52:16 +0530
Subject: [PATCH v47 1/2] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to
the physical standbys so that logical replication can be resumed after
failover. It is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API.

Ex1:
CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

Ex2: (failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

A new replication command called ALTER_REPLICATION_SLOT and a
corresponding walreceiver API function named walrcv_alter_slot have been
implemented. They allow subscribers or users to modify the failover
property of a replication slot on the publisher.

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).
---
 contrib/test_decoding/expected/slot.out       |  58 +++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |  20 +-
 doc/src/sgml/ref/create_subscription.sgml     |  23 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       | 114 +++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  38 +-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  53 ++-
 src/backend/replication/logical/worker.c      |  67 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 192 +++++++++-
 src/backend/replication/slotfuncs.c           |  25 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 358 +++++++++++++++++-
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_dump/pg_dump.c                     |  20 +-
 src/bin/pg_dump/pg_dump.h                     |   1 +
 src/bin/pg_upgrade/info.c                     |   5 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 .../t/050_standby_failover_slots_sync.pl      | 299 +++++++++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 ++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 50 files changed, 1569 insertions(+), 170 deletions(-)
 create mode 100644 src/test/recovery/t/050_standby_failover_slots_sync.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 44cada2b40..7993fe3cdd 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4360,6 +4360,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..481e397bad 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
@@ -230,6 +233,17 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
+
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property of the new slot may differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription. When creating the slot,
+      ensure the slot failover property matches the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter value of the subscription.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..fa6cd1c43f 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,29 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..e68f0d401e 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,38 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If the slot_name is specified without the create_slot option,
+			 * it is possible that the user intends to use an existing slot on
+			 * the publisher, so here we alter the failover property of the
+			 * slot to match the failover value in subscription.
+			 *
+			 * We do not need to change the failover to false if the server
+			 * does not support failover (e.g. pre-PG17)
+			 */
+			else if (opts.slot_name &&
+					 (failover_enabled || walrcv_server_version(wrconn) >= 170000))
+			{
+				bool		failover_delayed = (!failover_enabled && opts.failover);
+
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+								opts.slot_name, failover_enabled ? "true" : "false"),
+						 failover_delayed ?
+						 errdetail("The failover state will be set to true once table synchronization has been completed.")
+						 : 0));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,13 +1335,22 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
@@ -1334,13 +1399,26 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 						/* translator: %s is an SQL ALTER command */
 								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
 										 isadd ?
@@ -1389,7 +1467,19 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 					ereport(ERROR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "two_phase"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "failover"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 693b3669ba..cf11b98da3 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -888,8 +892,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -918,7 +922,8 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+			appendStringInfoString(&cmd, "FAILOVER, ");
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -987,6 +992,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 4d056c16c8..7b6170fe55 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -624,15 +624,28 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "two_phase")));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "failover")));
+
 				should_exit = true;
 			}
 		}
@@ -1430,7 +1443,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1732,10 +1746,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1743,9 +1759,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1759,9 +1774,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..e46a1955e8 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,6 +132,33 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * failover = true when creating the subscription. Enabling failover allows us
+ * to smoothly transition to the promoted standby, ensuring that we can
+ * subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
@@ -3947,6 +3974,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4510,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4568,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4608,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..49d2de5024 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -248,10 +260,13 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     during getting changes, if the two_phase option is enabled it can skip
  *     prepare because by that time start decoding point has been moved. So the
  *     user will only get commit prepared.
+ * failover: If enabled, allows the slot to be synced to physical standbys so
+ *     that logical replication can be resumed after failover.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +695,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2200,148 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+	bool		ok;
+
+	/* Need a modifiable copy of string. */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into a list of identifiers. */
+	ok = SplitIdentifierString(rawname, ',', &elemlist);
+
+	if (!ok)
+		GUC_check_errdetail("List syntax is invalid.");
+
+	/*
+	 * If there is a syntax error in the name or if the replication slots'
+	 * data is not initialized yet (i.e., we are in the startup process), skip
+	 * the slot verification.
+	 */
+	if (!ok || !ReplicationSlotCtl)
+	{
+		pfree(rawname);
+		list_free(elemlist);
+		return ok;
+	}
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			GUC_check_errdetail("replication slot \"%s\" does not exist",
+								name);
+			ok = false;
+			break;
+		}
+
+		if (!SlotIsPhysical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			ok = false;
+			break;
+		}
+	}
+
+	pfree(rawname);
+	list_free(elemlist);
+	return ok;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	/*
+	 * Since we do not support syncing slots to cascading standbys, we return
+	 * NIL here if we are running in a standby to indicate that no standby
+	 * slots need to be waited for.
+	 */
+	if (RecoveryInProgress())
+		return NIL;
+
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..c87f61666d 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -491,6 +501,12 @@ pg_logical_replication_slot_advance(XLogRecPtr moveto)
 											   .segment_close = wal_segment_close),
 									NULL, NULL, NULL);
 
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to moveto lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(moveto);
+
 		/*
 		 * Start reading at the slot's restart_lsn, which we know to point to
 		 * a valid record.
@@ -679,6 +695,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +751,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +791,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 26ded928a7..ca61a99785 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -387,7 +387,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..fddc9310c6 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool		failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,257 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up the logical walsender processes with failover-enabled slots if the
+ * physical slot of the current walsender is specified in standby_slot_names
+ * GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names;
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	/*
+	 * If we are running on a standby, there is no need to reload
+	 * standby_slot_names since we do not support syncing slots to cascading
+	 * standbys.
+	 */
+	if (RecoveryInProgress())
+		return;
+
+	pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn. Additionally,
+ * it removes slots that have been invalidated, dropped, or converted to
+ * logical slots.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			/*
+			 * It may happen that the slot specified in standby_slot_names GUC
+			 * value is dropped, so let's skip over it.
+			 */
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+		}
+		else if (SlotIsLogical(slot))
+		{
+			/*
+			 * If a logical slot name is provided in standby_slot_names, issue
+			 * a WARNING and skip it. Although logical slots are disallowed in
+			 * the GUC check_hook(validate_standby_slots), it is still
+			 * possible for a user to drop an existing physical slot and
+			 * recreate a logical slot with the same name. Since it is
+			 * harmless, a WARNING should be enough, no need to error-out.
+			 */
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+		}
+		else
+		{
+			SpinLockAcquire(&slot->mutex);
+
+			if (slot->data.invalidated != RS_INVAL_NONE)
+			{
+				/*
+				 * Specified physical slot have been invalidated, so no point
+				 * in waiting for it.
+				 */
+				warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+			}
+			else if (XLogRecPtrIsInvalid(slot->data.restart_lsn) ||
+					 slot->data.restart_lsn < wait_for_lsn)
+			{
+				bool	inactive = (slot->active_pid == 0);
+
+				SpinLockRelease(&slot->mutex);
+
+				/* Log warning if no active_pid for this physical slot */
+				if (inactive)
+					ereport(WARNING,
+							errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+								   name, "standby_slot_names"),
+							errdetail("Logical replication is waiting on the "
+									  "standby associated with \"%s\".", name),
+							errhint("Consider starting standby associated with "
+									"\"%s\" or amend standby_slot_names.", name));
+
+				/* Continue if the current slot hasn't caught up. */
+				continue;
+			}
+			else
+			{
+				Assert(slot->data.restart_lsn >= wait_for_lsn);
+			}
+
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving the given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	if (standby_slots == NIL)
+		return;
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, we also
+ * wait for all the specified streaming replication standby servers to
+ * confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr even when we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
-	if (RecentFlushPtr != InvalidXLogRecPtr &&
-		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	if (!XLogRecPtrIsInvalid(RecentFlushPtr) && loc <= RecentFlushPtr)
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know
+		 * we have enough WAL available and all the standby servers have
+		 * confirmed receipt of WAL up to RecentFlushPtr. This is particularly
+		 * interesting if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1849,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1864,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,9 +1903,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
+		{
+			/* Already caught up and doesn't need to wait for standby_slots. */
 			break;
+		}
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
 		WalSndCaughtUp = true;
@@ -1654,9 +1954,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2121,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2358,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3621,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3692,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another
+	 * CV that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f7c9882f7c..9a54e04821 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4571,6 +4571,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..5d940b72cd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -329,6 +329,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8c0b5486b9..373c2514df 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4618,6 +4618,7 @@ getSubscriptions(Archive *fout)
 	int			i_subsynccommit;
 	int			i_subpublications;
 	int			i_suborigin;
+	int			i_subfailoverstate;
 	int			i,
 				ntups;
 
@@ -4673,14 +4674,22 @@ getSubscriptions(Archive *fout)
 		appendPQExpBufferStr(query,
 							 " s.subpasswordrequired,\n"
 							 " s.subrunasowner,\n"
-							 " s.suborigin\n");
+							 " s.suborigin,\n");
 	else
 		appendPQExpBuffer(query,
 						  " 't' AS subpasswordrequired,\n"
 						  " 't' AS subrunasowner,\n"
-						  " '%s' AS suborigin\n",
+						  " '%s' AS suborigin,\n",
 						  LOGICALREP_ORIGIN_ANY);
 
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(query,
+							 " s.subfailoverstate\n");
+	else
+		appendPQExpBuffer(query,
+						  " '%c' AS subfailoverstate\n",
+						  LOGICALREP_FAILOVER_STATE_DISABLED);
+
 	appendPQExpBufferStr(query,
 						 "FROM pg_subscription s\n"
 						 "WHERE s.subdbid = (SELECT oid FROM pg_database\n"
@@ -4709,6 +4718,7 @@ getSubscriptions(Archive *fout)
 	i_subsynccommit = PQfnumber(res, "subsynccommit");
 	i_subpublications = PQfnumber(res, "subpublications");
 	i_suborigin = PQfnumber(res, "suborigin");
+	i_subfailoverstate = PQfnumber(res, "subfailoverstate");
 
 	subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo));
 
@@ -4746,6 +4756,8 @@ getSubscriptions(Archive *fout)
 		subinfo[i].subpublications =
 			pg_strdup(PQgetvalue(res, i, i_subpublications));
 		subinfo[i].suborigin = pg_strdup(PQgetvalue(res, i, i_suborigin));
+		subinfo[i].subfailoverstate =
+			pg_strdup(PQgetvalue(res, i, i_subfailoverstate));
 
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(subinfo[i].dobj), fout);
@@ -4771,6 +4783,7 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	int			npubnames = 0;
 	int			i;
 	char		two_phase_disabled[] = {LOGICALREP_TWOPHASE_STATE_DISABLED, '\0'};
+	char		failover_disabled[] = {LOGICALREP_FAILOVER_STATE_DISABLED, '\0'};
 
 	/* Do nothing in data-only dump */
 	if (dopt->dataOnly)
@@ -4818,6 +4831,9 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	if (strcmp(subinfo->subtwophasestate, two_phase_disabled) != 0)
 		appendPQExpBufferStr(query, ", two_phase = on");
 
+	if (strcmp(subinfo->subfailoverstate, failover_disabled) != 0)
+		appendPQExpBufferStr(query, ", failover = true");
+
 	if (strcmp(subinfo->subdisableonerr, "t") == 0)
 		appendPQExpBufferStr(query, ", disable_on_error = true");
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2fe3cbed9a..f62a4dfd4b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -671,6 +671,7 @@ typedef struct _SubscriptionInfo
 	char	   *subsynccommit;
 	char	   *subpublications;
 	char	   *suborigin;
+	char	   *subfailoverstate;
 } SubscriptionInfo;
 
 /*
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..e16286f18c 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,6 +679,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
@@ -687,6 +688,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +699,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..2d8bcb26f9 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced to the
+								 * physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 020e7aa1cc..cb3a2b3aed 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -159,7 +159,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -179,8 +179,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 049801186c..905964a2e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3327,7 +3327,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 77e8b13764..bbc03bb76b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..5ddad69348 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)? Only
+	 * relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 949e874f21..f1135762fb 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -355,9 +355,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									  const char *slotname,
+									  bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -399,6 +410,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -428,8 +440,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 60313980a9..ecd212c7b6 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -12,6 +12,8 @@
 #ifndef _WALSENDER_H
 #define _WALSENDER_H
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -45,6 +47,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index db73408937..84bb79ac0f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -256,7 +256,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..083b558448 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_standby_failover_slots_sync.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
new file mode 100644
index 0000000000..bd6affaf48
--- /dev/null
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -0,0 +1,299 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+##################################################
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+##################################################
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on the primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres',
+	"CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+
+# Create a table and a subscription with failover = true
+$subscriber1->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);
+]);
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	CREATE SUBSCRIPTION regress_mysub2 CONNECTION '$publisher_connstr' PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);
+]);
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with the specified physical replication slot so
+# that the logical replication slot won't receive changes until the standby
+# comes up.
+$standby1->stop;
+
+# Create some data on the primary
+my $primary_row_count   = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('regress_mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary until standby1 acknowledges changes"
+);
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 acknowledges changes");
+
+# Stop the standby associated with the specified physical replication slot so
+# that the logical replication slot won't receive changes until the standby
+# slot's restart_lsn is advanced or the slot is removed from the
+# standby_slot_names list
+$publisher->safe_psql('postgres', "TRUNCATE tab_int;");
+$publisher->wait_for_catchup('regress_mysub1');
+$standby1->stop;
+
+##################################################
+# Verify that when using pg_logical_slot_get_changes to consume changes from a
+# logical slot with failover enabled, it will also wait for the slots specified
+# in standby_slot_names to catch up.
+##################################################
+
+# Create a logical 'test_decoding' replication slot with failover enabled
+$publisher->safe_psql('postgres',
+	"SELECT pg_create_logical_replication_slot('test_slot', 'test_decoding', false, false, true);"
+);
+
+my $back_q = $primary->background_psql('postgres', on_error_stop => 0);
+my $pid = $back_q->query('SELECT pg_backend_pid()');
+
+# Try and get changes from the logical slot with failover enabled.
+my $offset = -s $primary->logfile;
+$back_q->query_until(qr//,
+	"SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL);\n");
+
+# Wait until the primary server logs a warning indicating that it is waiting
+# for the sb1_slot to catch up.
+$primary->wait_for_log(
+	qr/WARNING: ( [A-Z0-9]+:)? replication slot \"sb1_slot\" specified in parameter \"standby_slot_names\" does not have active_pid/,
+	$offset);
+
+ok($primary->safe_psql('postgres', "SELECT pg_cancel_backend($pid)"),
+	"cancelling pg_logical_slot_get_changes command");
+
+$publisher->safe_psql('postgres',
+	"SELECT pg_drop_replication_slot('test_slot');"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we manually advance this slot's LSN to the
+# latest position.
+##################################################
+
+# Create some data on the primary
+$primary_row_count = 10;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# The subscription that's up and running and is enabled for failover doesn't
+# get the data from primary and keeps waiting for the standby specified in
+# standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary as long as standby1 restart_lsn has not been updated"
+);
+
+# Advance the lsn of the standby slot manually
+$primary->safe_psql('postgres',
+	"SELECT * FROM pg_replication_slot_advance('sb1_slot', pg_current_wal_lsn());"
+);
+
+# Now that the standby lsn has advanced, primary must send the decoded
+# changes to the subscription
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1's restart_lsn has been updated"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we remove the slot from standby_slot_names.
+##################################################
+
+# Create some data on the primary
+$primary_row_count = 20;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(11,20);");
+
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 10 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data as the sb1_slot doesn't catch up");
+
+# Remove the standby from the standby_slot_names list and reload the
+# configuration
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', "''");
+$primary->psql('postgres', "SELECT pg_reload_conf()");
+
+# Now that the standby lsn has advanced, the primary must send the decoded
+# changes to the subscription.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 is removed from the standby_slot_names list"
+);
+
+# Put the standby back on the primary_slot_name for the rest of the tests
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', 'sb1_slot');
+$primary->restart();
+
+##################################################
+# Test that when a subscription with failover enabled is created, it will alter
+# the failover property of the corresponding slot on the publisher.
+##################################################
+
+# Create a slot on the publisher with failover disabled
+$primary->safe_psql('postgres',
+	"SELECT 'init' FROM pg_create_logical_replication_slot('lsub3_slot', 'pgoutput', false, false, false);"
+);
+
+# Confirm that the failover flag on the slot is turned off
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"f",
+	'logical slot has failover false on the primary');
+
+# Create another subscription (using the same slot created above) that enables
+# failover.
+$subscriber1->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub3 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub3_slot, copy_data=false, failover = true, create_slot = false);"
+);
+
+# Confirm that the failover flag on the slot has now been turned on
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"t",
+	'logical slot has failover true on the primary');
+
+$subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ba41149b88..199863c6b5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3870,6 +3871,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.34.1



  [application/octet-stream] v47-0002-Add-logical-slot-sync-capability-to-the-physical.patch (92.2K, ../../CAJpy0uBgGRJvWYfJD=3XE8OZ0V=X1keOYXxXEkpJqYk00BAzZg@mail.gmail.com/3-v47-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From 6d98674cc7bc4efcb9741f297addedad84f15891 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Tue, 12 Dec 2023 16:55:06 +0800
Subject: [PATCH v47 2/2] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover. All the failover logical
replication slots on the primary (assuming configurations are
appropriate) are automatically created on the physical standbys and
are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The nap time of the worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. If
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated.

If a logical slot on the primary is valid but is invalidated on the standby,
then that slot is dropped and recreated on the standby in next sync-cycle. It
is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently). This situation may occur due to the
following reasons:
- The max_slot_wal_keep_size on the standby is insufficient to retain WAL
  records from the restart_lsn of the slot.
- primary_slot_name is temporarily reset to null and the physical slot is
  removed.
- The primary changes wal_level to a level lower than logical.

The slots synchronization status on the standby can be monitored using
'sync_state' column of pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but slot is not ready yet for periodic syncs,
'r': ready for periodic syncs.
---
 doc/src/sgml/bgworker.sgml                    |   64 +-
 doc/src/sgml/config.sgml                      |   32 +-
 doc/src/sgml/logicaldecoding.sgml             |   33 +
 doc/src/sgml/system-views.sgml                |   26 +
 src/backend/access/transam/xlogrecovery.c     |   10 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    4 +
 src/backend/postmaster/postmaster.c           |   10 +
 .../libpqwalreceiver/libpqwalreceiver.c       |   43 +-
 src/backend/replication/logical/Makefile      |    1 +
 src/backend/replication/logical/logical.c     |   25 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1326 +++++++++++++++++
 src/backend/replication/logical/tablesync.c   |    1 +
 src/backend/replication/logical/worker.c      |   15 +-
 src/backend/replication/slot.c                |   24 +-
 src/backend/replication/slotfuncs.c           |   37 +-
 src/backend/replication/walsender.c           |    6 +-
 src/backend/storage/ipc/ipci.c                |    2 +
 src/backend/tcop/postgres.c                   |   11 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   10 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/commands/subscriptioncmds.h       |    4 +
 src/include/postmaster/bgworker.h             |    1 +
 src/include/replication/logicalworker.h       |    1 +
 src/include/replication/slot.h                |   22 +-
 src/include/replication/walreceiver.h         |   19 +
 src/include/replication/worker_internal.h     |   11 +
 .../t/050_standby_failover_slots_sync.pl      |  203 +++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    2 +
 34 files changed, 1932 insertions(+), 36 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2c393385a9..ecde4fa61f 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -114,18 +114,58 @@ typedef struct BackgroundWorker
 
   <para>
    <structfield>bgw_start_time</structfield> is the server state during which
-   <command>postgres</command> should start the process; it can be one of
-   <literal>BgWorkerStart_PostmasterStart</literal> (start as soon as
-   <command>postgres</command> itself has finished its own initialization; processes
-   requesting this are not eligible for database connections),
-   <literal>BgWorkerStart_ConsistentState</literal> (start as soon as a consistent state
-   has been reached in a hot standby, allowing processes to connect to
-   databases and run read-only queries), and
-   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system has
-   entered normal read-write state).  Note the last two values are equivalent
-   in a server that's not a hot standby.  Note that this setting only indicates
-   when the processes are to be started; they do not stop when a different state
-   is reached.
+   <command>postgres</command> should start the process. Note that this setting
+   only indicates when the processes are to be started; they do not stop when
+   a different state is reached. Possible values are:
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>BgWorkerStart_PostmasterStart</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_PostmasterStart</primary></indexterm>
+       Start as soon as postgres itself has finished its own initialization;
+       processes requesting this are not eligible for database connections.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState</primary></indexterm>
+       Start as soon as a consistent state has been reached in a hot-standby,
+       allowing processes to connect to databases and run read-only queries.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_RecoveryFinished</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_RecoveryFinished</primary></indexterm>
+       Start as soon as the system has entered normal read-write state. Note
+       that the <literal>BgWorkerStart_ConsistentState</literal> and
+      <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
+       in a server that's not a hot standby.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState_HotStandby</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState_HotStandby</primary></indexterm>
+       Same meaning as <literal>BgWorkerStart_ConsistentState</literal> but
+       it is more strict in terms of the server i.e. start the worker only
+       if it is hot-standby.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
   </para>
 
   <para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 7993fe3cdd..e14953e4dd 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4373,6 +4373,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must configure
+        <varname>enable_syncslot</varname> = true so they can receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4568,8 +4574,12 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           <varname>primary_conninfo</varname> string, or in a separate
           <filename>~/.pgpass</filename> file on the standby server (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          If slot synchronization is enabled then it is also necessary to
+          specify <literal>dbname</literal> in the
+          <varname>primary_conninfo</varname> string. This will only be used for
+          slot synchronization. It is ignored for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4894,6 +4904,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..43537e00b2 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,39 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and setting
+     <varname>enable_syncslot</varname> on the standby. For the synchronization
+     to work, it is mandatory to have a physical replication slot between the
+     primary and the standby. It's highly recommended that the said physical
+     replication slot is listed in <varname>standby_slot_names</varname> on
+     the primary to prevent the subscriber from consuming changes faster than
+     the hot standby. Additionally, <varname>hot_standby_feedback</varname>
+     must be enabled on the standby for the slots synchronization to work.
+    </para>
+
+    <para>
+     By enabling synchronization of slots, logical replication can be resumed
+     after failover depending upon the
+     <link linkend="view-pg-replication-slots">pg_replication_slots</link>.<structfield>sync_state</structfield>
+     for the synchronized slots on the standby at the time of failover.
+     Only slots that were in ready sync_state ('r') on the standby before
+     failover can be used for logical replication after failover.
+     However, the slots which were in initiated sync_state ('i') and
+     not sync-ready ('r') at the time of failover will be dropped and
+     logical replication for such slots can not be resumed after failover.
+     This applies to the case where a logical subscription is disabled
+     before failover and is enabled after failover. If the synchronized
+     slot due to disabled subscription could not be made sync-ready ('r')
+     on standby, then the subscription can not be resumed after failover
+     even when enabled.
+     If the primary is idle, then the synchronized slots on the standby may
+     take a noticeable time to reach the ready ('r') sync_state. This can
+     be sped up by calling the
+     <function>pg_log_standby_snapshot</function> function on the primary.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..c906f2186e 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,32 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has configured <varname>enable_syncslot</varname> = true
+      </para>
+      <para>
+       State code:
+       <literal>n</literal> = none for user created slots,
+       <literal>i</literal> = sync initiated for the slot but slot is not ready
+        yet for periodic syncs,
+       <literal>r</literal> = ready for periodic syncs.
+      </para>
+      <para>
+      The hot standby can have any of these sync_state for the slots but on a
+      hot standby, the slots with state 'r' and 'i' can neither be used for
+      logical decoding nor dropped by the user. The primary server will have
+      sync_state as 'n' for all the slots. But if the standby is promoted to
+      become the new primary server, sync_state can be seen 'r' as well. On
+      this new primary server, slots with sync_state as 'r' and 'n' will
+      behave the same.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index a2c8fa3981..c867cfbc63 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1436,15 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 3c99cf6047..7f74f53ad1 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -21,6 +21,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "replication/worker_internal.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -129,6 +130,9 @@ static const struct
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 651b85ea74..9b74a49663 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -115,6 +115,7 @@
 #include "postmaster/syslogger.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -1003,6 +1004,12 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	/*
+	 * Register the slot sync worker here to kick start slot-sync operation
+	 * sooner on the physical standby.
+	 */
+	SlotSyncWorkerRegister();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
@@ -5740,6 +5747,9 @@ bgworker_should_start_now(BgWorkerStartTime start_time)
 		case PM_HOT_STANDBY:
 			if (start_time == BgWorkerStart_ConsistentState)
 				return true;
+			if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
+					 pmState != PM_RUN)
+				return true;
 			/* fall through */
 
 		case PM_RECOVERY:
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index cf11b98da3..48b889173a 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -418,6 +421,44 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
@@ -997,7 +1038,7 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
  */
 static void
 libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
-					bool failover)
+					 bool failover)
 {
 	StringInfoData cmd;
 	PGresult   *res;
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..c21d081346 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,31 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	if (RecoveryInProgress())
+	{
+		/*
+		 * Do not allow consumption of a "synchronized" slot until the standby
+		 * gets promoted.
+		 */
+		if (slot->data.sync_state != SYNCSLOT_STATE_NONE)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot use replication slot \"%s\" for logical "
+						"decoding", NameStr(slot->data.name)),
+					 errdetail("This slot is being synced from the primary server."),
+					 errhint("Specify another replication slot.")));
+	}
+	else
+	{
+		/*
+		 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+		 * promotion.
+		 */
+		if (slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			elog(ERROR, "replication slot \"%s\" was not synced completely "
+				"from the primary server", NameStr(slot->data.name));
+	}
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..c94179cc98
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1326 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * While creating the slot on physical standby, if the local restart_lsn and/or
+ * local catalog_xmin is ahead of those on the remote then the worker cannot
+ * create the local slot in sync with the primary server because that would
+ * mean moving the local slot backwards and the standby might not have WALs
+ * retained for old LSN. In this case, the worker will wait for the primary
+ * server slot's restart_lsn and catalog_xmin to catch up with the local one
+ * before attempting the actual sync. Meanwhile, it will persist the slot with
+ * sync_state as SYNCSLOT_STATE_INITIATED('i'). Once the primary server catches
+ * up, it will move the slot to SYNCSLOT_STATE_READY('r') state and will perform
+ * the sync periodically.
+ *
+ * The worker also takes care of dropping the slots which were created by it
+ * and are currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/table.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_database.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/*
+ * Struct for sharing information between startup process and slot
+ * sync worker.
+ *
+ * Slot sync worker's pid is needed by startup process in order to
+ * shut it down during promotion.
+ */
+typedef struct SlotSyncWorkerCtx
+{
+	pid_t		pid;
+	slock_t		mutex;
+} SlotSyncWorkerCtx;
+
+SlotSyncWorkerCtx *SlotSyncWorker = NULL;
+
+/* GUC variable */
+bool		enable_syncslot = false;
+
+/* The last sync-cycle time when the worker updated any of the slots. */
+static TimestampTz last_update_time;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+static bool ProcessSlotSyncInterrupts(WalReceiverConn *wrconn,
+									  bool check_cascading_standby);
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * If passed, *wait_attempts_exceeded will be set to true only if this
+ * function exits after exhausting its wait attempts. It will be false
+ * in all the other cases like failure, remote-slot invalidation, primary
+ * could catch up.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *wait_attempts_exceeded)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
+
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn,"
+					 " confirmed_flush_lsn, catalog_xmin"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		XLogRecPtr	new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *slot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Handle any termination request if any */
+		ProcessSlotSyncInterrupts(wrconn, false /* check_cascading_standby */ );
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot \"%s\" info from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" creation aborted", remote_slot->name),
+					 errdetail("This slot was not found on the primary server")));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(slot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" creation aborted", remote_slot->name),
+					 errdetail("This slot was invalidated on the primary server")));
+			pfree(cmd.data);
+			ExecClearTuple(slot);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * Having got a valid restart_lsn, the confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(slot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\"",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			if (wait_attempts_exceeded)
+				*wait_attempts_exceeded = true;
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up (refer
+ * to the comment atop the file for details on this wait)
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			local_slots = lappend(local_slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(local_slots);
+}
+
+/*
+ * Get list of local logical slots which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slots(void)
+{
+	List	   *local_slots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			local_slots = lappend(local_slots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return local_slots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+check_sync_slot_on_remote(ReplicationSlot *local_slot, List *remote_slots,
+						  bool *locally_invalidated)
+{
+	ListCell   *lc;
+
+	foreach(lc, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			SpinLockAcquire(&local_slot->mutex);
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+			SpinLockRelease(&local_slot->mutex);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Additionally, it drops slots that are valid on the primary but got
+ * invalidated on the standby. This situation may occur due to the following
+ * reasons:
+ * - The max_slot_wal_keep_size on the standby is insufficient to retain WAL
+ *   records from the restart_lsn of the slot.
+ * - primary_slot_name is temporarily reset to null and the physical slot is
+ *   removed.
+ * - The primary changes wal_level to a level lower than logical.
+ *
+ * The assumption is that these dropped local invalidated slots will get
+ * recreated in next sync-cycle and it is okay to drop and recreate such slots
+ * as long as these are not consumable on the standby (which is the case
+ * currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	local_slots = get_local_synced_slots();
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc);
+		bool		remote_exists = false;
+		bool		locally_invalidated = false;
+
+		remote_exists = check_sync_slot_on_remote(local_slot, remote_slot_list,
+												  &locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!remote_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ */
+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+					 bool *slot_updated)
+{
+	ReplicationSlot *s;
+	ReplicationSlot *slot;
+	char		sync_state = '\0';
+
+	/*
+	 * Sanity check: Make sure that concerned WAL is received before syncing
+	 * slot to target lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* Search for the named slot */
+	if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		SpinLockAcquire(&s->mutex);
+		sync_state = s->data.sync_state;
+		SpinLockRelease(&s->mutex);
+
+		/* User created slot with the same name exists, raise ERROR. */
+		if (sync_state == SYNCSLOT_STATE_NONE)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("skipping sync of slot \"%s\" as it is a user created"
+							" slot", remote_slot->name),
+					 errdetail("This slot has failover enabled on the primary and"
+							   " thus is sync candidate but user created slot with"
+							   " the same name already exists on the standby")));
+		}
+	}
+
+	/* Slot created by the slot sync worker exists, sync it */
+	if (sync_state)
+	{
+		Assert(sync_state == SYNCSLOT_STATE_READY ||
+			   sync_state == SYNCSLOT_STATE_INITIATED);
+
+		/*
+		 * It is important to acquire the slot here before checking
+		 * invalidation. If we don't acquire the slot first, there could be a
+		 * race condition that the local slot could be invalidated just after
+		 * checking the 'invalidated' flag here and we could end up
+		 * overwriting 'invalidated' flag to remote_slot's value. See
+		 * InvalidatePossiblyObsoleteSlot() where it invalidates slot directly
+		 * if the slot is not acquired by other processes.
+		 */
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		slot = MyReplicationSlot;
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (slot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&slot->mutex);
+			slot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Skip the sync of an invalidated slot */
+		if (slot->data.invalidated != RS_INVAL_NONE)
+		{
+			ReplicationSlotRelease();
+			return;
+		}
+
+		/* Slot ready for sync, so sync it. */
+		if (sync_state == SYNCSLOT_STATE_READY)
+		{
+			/*
+			 * Sanity check: With hot_standby_feedback enabled and
+			 * invalidations handled apropriately as above, this should never
+			 * happen.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn)
+			{
+				elog(ERROR,
+					 "not synchronizing local slot \"%s\" LSN(%X/%X)"
+					 " to remote slot's LSN(%X/%X) as synchronization "
+					 " would move it backwards", remote_slot->name,
+					 LSN_FORMAT_ARGS(slot->data.restart_lsn),
+					 LSN_FORMAT_ARGS(remote_slot->restart_lsn));
+			}
+
+			if (remote_slot->confirmed_lsn != slot->data.confirmed_flush ||
+				remote_slot->restart_lsn != slot->data.restart_lsn ||
+				remote_slot->catalog_xmin != slot->data.catalog_xmin)
+			{
+				/* Update LSN of slot to remote slot's current position */
+				local_slot_update(remote_slot);
+				ReplicationSlotSave();
+				*slot_updated = true;
+			}
+		}
+
+		/* Slot not ready yet, let's attempt to make it sync-ready now. */
+		else if (sync_state == SYNCSLOT_STATE_INITIATED)
+		{
+			/*
+			 * Wait for the primary server to catch-up. Refer to the comment
+			 * atop the file for details on this wait.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+				TransactionIdPrecedes(remote_slot->catalog_xmin,
+									  slot->data.catalog_xmin))
+			{
+				if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+				{
+					ReplicationSlotRelease();
+					return;
+				}
+			}
+
+			/*
+			 * Wait for primary is over, update the lsns and mark the slot as
+			 * READY for further syncs.
+			 */
+			local_slot_update(remote_slot);
+			SpinLockAcquire(&slot->mutex);
+			slot->data.sync_state = SYNCSLOT_STATE_READY;
+			SpinLockRelease(&slot->mutex);
+
+			/* Save the changes */
+			ReplicationSlotMarkDirty();
+			ReplicationSlotSave();
+			*slot_updated = true;
+
+			ereport(LOG,
+					errmsg("newly locally created slot \"%s\" is sync-ready now",
+						   remote_slot->name));
+		}
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+
+		/* Ensure that we have transaction env needed by get_database_oid() */
+		Assert(IsTransactionState());
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover,
+							  SYNCSLOT_STATE_INITIATED);
+
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * Wait for the primary server to catch-up. Refer to the comment atop
+		 * the file for details on this wait.
+		 */
+		if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  slot->data.catalog_xmin))
+		{
+			bool		wait_attempts_exceeded = false;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &wait_attempts_exceeded))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid) and attempt the wait and synchronization in
+				 * the next cycle.
+				 */
+				if (wait_attempts_exceeded)
+				{
+					ReplicationSlotPersist();
+					*slot_updated = true;
+				}
+
+				ReplicationSlotRelease();
+				return;
+			}
+		}
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&slot->mutex);
+		slot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&slot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		*slot_updated = true;
+
+		ereport(LOG,
+				errmsg("newly locally created slot \"%s\" is sync-ready now",
+					   remote_slot->name));
+	}
+
+	ReplicationSlotRelease();
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and updates
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns TRUE if any of the slots gets updated in this sync-cycle.
+ */
+static bool
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *tupslot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	ListCell   *lc;
+	bool		slot_updated = false;
+
+	/*
+	 * The primary_slot_name is not set yet or WALs not received yet.
+	 * Synchronization is not possible if the walreceiver is not started.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		return slot_updated;
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	initStringInfo(&s);
+
+	/* Construct query to fetch slots with failover enabled. */
+	appendStringInfo(&s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover");
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Ensure that we have transaction env needed by walrcv_exec() */
+	Assert(IsTransactionState());
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(tupslot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(tupslot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(tupslot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(tupslot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(tupslot, 5,
+																	   &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(tupslot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(tupslot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(tupslot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(tupslot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(tupslot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(lc, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		synchronize_one_slot(wrconn, remote_slot, &slot_updated);
+	}
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	return slot_updated;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker",
+							&err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ *
+ * Validates the primary server info.
+ *
+ * Using the specified primary server connection, it verifies whether the master
+ * is a standby itself and returns true in that case to convey the caller that
+ * we are on the cascading standby.
+ * But if master is the primary server, it goes ahead and validates
+ * primary_slot_name. It emits error if the physical slot in primary_slot_name
+ * does not exist on the primary server.
+ */
+static bool
+validate_primary_info(WalReceiverConn *wrconn)
+{
+#define PRIMARY_INFO_OUTPUT_COL_COUNT 2
+	WalRcvExecResult *res;
+	Oid			slotRow[PRIMARY_INFO_OUTPUT_COL_COUNT] = {BOOLOID, BOOLOID};
+	StringInfoData cmd;
+	bool		isnull;
+	TupleTableSlot *slot;
+	bool		valid;
+	bool		master_in_recovery;
+	bool		tuple_ok PG_USED_FOR_ASSERTS_ONLY;
+
+	/* Ensure that we have transaction env needed by walrcv_exec() */
+	Assert(IsTransactionState());
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT pg_is_in_recovery(), count(*) = 1"
+					 " FROM pg_replication_slots"
+					 " WHERE slot_type='physical' and slot_name=%s",
+					 quote_literal_cstr(PrimarySlotName));
+
+	res = walrcv_exec(wrconn, cmd.data, PRIMARY_INFO_OUTPUT_COL_COUNT, slotRow);
+	pfree(cmd.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch recovery and primary_slot_name \"%s\" info from the "
+						"primary: %s", PrimarySlotName, res->err)));
+
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	tuple_ok = tuplestore_gettupleslot(res->tuplestore, true, false, slot);
+	Assert(tuple_ok);			/* It must return one tuple */
+
+	master_in_recovery = DatumGetBool(slot_getattr(slot, 1, &isnull));
+	Assert(!isnull);
+
+	/* No need to check further, return that we are cascading standby */
+	if (master_in_recovery)
+		return true;
+
+	valid = DatumGetBool(slot_getattr(slot, 2, &isnull));
+	Assert(!isnull);
+
+	if (!valid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("exiting from slot synchronization due to bad configuration"),
+		/* translator: second %s is a GUC variable name */
+				 errdetail("The primary slot \"%s\" specified by %s is not valid.",
+						   PrimarySlotName, "primary_slot_name")));
+	ExecClearTuple(slot);
+	walrcv_clear_result(res);
+
+	return false;
+}
+
+/*
+ * Checks if GUCs are set appropriately before starting slot sync worker
+ *
+ * The slot sync worker can not start if 'enable_syncslot' is off and
+ * since 'enable_syncslot' is ON, check that the other GUC settings
+ * (primary_slot_name, hot_standby_feedback, wal_level, primary_conninfo)
+ * are compatible with slot synchronization. If not, raise ERROR.
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_slot_name"));
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be enabled.", "hot_standby_feedback"));
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("wal_level must be >= logical."));
+
+	/*
+	 * The primary_conninfo is required to make connection to primary for
+	 * getting slots information.
+	 */
+	if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_conninfo"));
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	*dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (*dbname == NULL)
+		ereport(ERROR,
+		/*
+		 * translator: 'dbname' is a specific option; %s is a GUC variable
+		 * name
+		 */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("'dbname' must be specified in %s.", "primary_conninfo"));
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs have changed, re-validate them. The
+ * worker will exit if the check fails.
+ *
+ * Returns TRUE if primary_slot_name is changed, let the caller re-verify it.
+ */
+static bool
+slotsync_reread_config(WalReceiverConn *wrconn)
+{
+	char	   *old_primary_conninfo = pstrdup(PrimaryConnInfo);
+	char	   *old_primary_slot_name = pstrdup(PrimarySlotName);
+	bool		old_hot_standby_feedback = hot_standby_feedback;
+	bool		revalidate = false;
+	char	   *old_dbname;
+	bool		conninfoChanged;
+	bool		slotnameChanged;
+
+	old_dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	Assert(old_dbname);
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	conninfoChanged = strcmp(old_primary_conninfo, PrimaryConnInfo) != 0;
+	slotnameChanged = strcmp(old_primary_slot_name, PrimarySlotName) != 0;
+
+	revalidate = conninfoChanged || slotnameChanged ||
+		(old_hot_standby_feedback != hot_standby_feedback);
+
+	pfree(old_primary_conninfo);
+	pfree(old_primary_slot_name);
+
+	if (revalidate)
+	{
+		char	   *new_dbname;
+
+		validate_slotsync_parameters(&new_dbname);
+
+		/*
+		 * Since we have initialized this worker with the old dbname, thus
+		 * exit if dbname changed. Let it get restarted and connect to the new
+		 * dbname specified.
+		 */
+		if (conninfoChanged && strcmp(old_dbname, new_dbname) != 0)
+			ereport(ERROR,
+					errmsg("exiting slot sync worker as dbname in "
+						   "primary_conninfo changed"));
+	}
+
+	return slotnameChanged;
+}
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static bool
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn, bool check_cascading_standby)
+{
+	bool		primary_slot_changed = false;
+
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting"
+					   " down on receiving SIGINT"));
+
+		walrcv_disconnect(wrconn);
+		proc_exit(0);
+	}
+
+	if (ConfigReloadPending)
+		primary_slot_changed = slotsync_reread_config(wrconn);
+
+	if (primary_slot_changed || check_cascading_standby)
+		return validate_primary_info(wrconn);
+
+	return false;
+}
+
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = InvalidPid;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+	bool		am_cascading_standby;
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	Assert(SlotSyncWorker->pid == InvalidPid);
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	validate_slotsync_parameters(&dbname);
+
+	/*
+	 * Connect to the database specified by user in primary_conninfo. We need
+	 * a database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/*
+	 * Using the existing primary server connection, validate the slot
+	 * specified in primary_slot_name and figure out if we are on cascading
+	 * standby. The transaction env is needed by syscache access in
+	 * validate_primary_info(), so start one.
+	 */
+	StartTransactionCommand();
+	am_cascading_standby = validate_primary_info(wrconn);
+	CommitTransactionCommand();
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+		TimestampTz now;
+		bool		slot_updated;
+
+		/*
+		 * The transaction env is needed by walrcv_exec() in both the slot
+		 * sync and primary info validation flow.
+		 */
+		StartTransactionCommand();
+
+		if (!am_cascading_standby)
+		{
+			slot_updated = synchronize_slots(wrconn);
+
+			/*
+			 * If any of the slots get updated in this sync-cycle, use default
+			 * naptime and update 'last_update_time'. But if no activity is
+			 * observed in this sync-cycle, then increase naptime provided
+			 * inactivity time reaches threshold.
+			 */
+			now = GetCurrentTimestamp();
+			if (slot_updated)
+				last_update_time = now;
+			else if (TimestampDifferenceExceeds(last_update_time,
+												now, WORKER_INACTIVITY_THRESHOLD_MS))
+				naptime = WORKER_INACTIVITY_NAPTIME_MS;
+		}
+		else
+			naptime = 6 * WORKER_INACTIVITY_NAPTIME_MS; /* 60 sec */
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+
+		am_cascading_standby =
+			ProcessSlotSyncInterrupts(wrconn, am_cascading_standby);
+
+		CommitTransactionCommand();
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsLogicalSlotSyncWorker(void)
+{
+	return SlotSyncWorker->pid == MyProcPid;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (SlotSyncWorker->pid == InvalidPid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Wait for it to die. */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long. */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (SlotSyncWorker->pid == InvalidPid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Allocate and initialize slot sync worker shared memory
+ */
+void
+SlotSyncWorkerShmemInit(void)
+{
+	Size		size;
+	bool		found;
+
+	size = sizeof(SlotSyncWorkerCtx);
+	size = MAXALIGN(size);
+
+	SlotSyncWorker = (SlotSyncWorkerCtx *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
+
+	if (!found)
+	{
+		memset(SlotSyncWorker, 0, size);
+		SlotSyncWorker->pid = InvalidPid;
+		SpinLockInit(&SlotSyncWorker->mutex);
+	}
+}
+
+/*
+ * Register the background worker for slots synchronization provided
+ * enable_syncslot is ON.
+ */
+void
+SlotSyncWorkerRegister(void)
+{
+	BackgroundWorker bgw;
+
+	if (!enable_syncslot)
+	{
+		ereport(LOG,
+				errmsg("skipping slots synchronization because enable_syncslot is "
+					   "disabled."));
+		return;
+	}
+
+	memset(&bgw, 0, sizeof(bgw));
+
+	/* We need database connection which needs shared-memory access as well. */
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+
+	/* Start as soon as a consistent state has been reached in a hot standby */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState_HotStandby;
+
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "replication slot sync worker");
+	snprintf(bgw.bgw_type, BGW_MAXLEN,
+			 "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
+	bgw.bgw_notify_pid = 0;
+	bgw.bgw_main_arg = (Datum) 0;
+
+	RegisterBackgroundWorker(&bgw);
+}
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 7b6170fe55..64fcfdac9a 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "commands/subscriptioncmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e46a1955e8..7b3784c212 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -141,7 +141,20 @@
  * subscribe to the new primary without losing any data.
  *
  * However, we do not enable failover for slots created by the table sync
- * worker.
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby due to the following reasons:
+ *
+ * - The standby needs to wait for the primary server to catch up because the
+ *   local restart_lsn of the newly created slot on the standby is set using
+ *   the latest redo position (GetXLogReplayRecPtr()), which is typically ahead
+ *   of the primary's restart_lsn.
+ * - The table sync slot's restart_lsn won't be advanced until the state
+ *   becomes SUBREL_STATE_CATCHUP.
+ *
+ * Therefore, if a failover happens before the restart_lsn advances, the table
+ * sync slot will not be synced to the standby. Consequently, we will not be
+ * able to subscribe to the promoted standby due to the absence of the
+ * necessary table sync slot.
  *
  * Additionally, failover is not enabled for the main slot if the table sync is
  * in progress. This is because if a failover occurs while the table sync
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 49d2de5024..a21a57c09c 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -262,11 +263,15 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     user will only get commit prepared.
  * failover: If enabled, allows the slot to be synced to physical standbys so
  *     that logical replication can be resumed after failover.
+ * sync_state: Defines slot synchronization state. For user created slots, it
+ *     is SYNCSLOT_STATE_NONE and for the slots being synchronized on the
+ *     physical standby, it is either SYNCSLOT_STATE_INITIATED or
+ *     SYNCSLOT_STATE_READY.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover)
+					  bool two_phase, bool failover, char sync_state)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -327,6 +332,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = sync_state;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -686,12 +692,26 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+	{
+		ReplicationSlotRelease();
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary server.")));
+	}
+
 	ReplicationSlotDropAcquired();
 }
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index c87f61666d..e5ba5956aa 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -44,7 +44,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false);
+						  false, SYNCSLOT_STATE_NONE);
 
 	if (immediately_reserve)
 	{
@@ -137,7 +137,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover);
+						  failover, SYNCSLOT_STATE_NONE);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fddc9310c6..8d48d7c8f4 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1071,7 +1071,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false);
+							  false, false, SYNCSLOT_STATE_NONE);
 
 		if (reserve_wal)
 		{
@@ -1102,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover);
+							  two_phase, failover, SYNCSLOT_STATE_NONE);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, true);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0e0ac22bdd..ae2daff87b 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -37,6 +37,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -339,6 +340,7 @@ CreateOrAttachShmemStructs(void)
 	WalRcvShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7298a187d1..d87020821c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3286,6 +3286,17 @@ ProcessInterrupts(void)
 			 */
 			proc_exit(1);
 		}
+		else if (IsLogicalSlotSyncWorker())
+		{
+			elog(DEBUG1,
+				"replication slot sync worker is shutting down due to administrator command");
+
+			/*
+			 * Slot sync worker can be stopped at any time.
+			 * Use exit status 1 so the background worker is restarted.
+			 */
+			proc_exit(1);
+		}
 		else if (IsBackgroundWorker)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 9a54e04821..7d50971fd7 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2020,6 +2021,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_POSTMASTER, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5d940b72cd..39224137e1 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -358,6 +358,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = off			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bbc03bb76b..82a11e4a31 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 214dc6c29e..75b4b2040d 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -17,6 +17,7 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"
 
 extern ObjectAddress CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										bool isTopLevel);
@@ -28,4 +29,7 @@ extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
 extern char defGetStreamingMode(DefElem *def);
 
+extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn,
+										char *slotname, bool missing_ok);
+
 #endif							/* SUBSCRIPTIONCMDS_H */
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index e90ff376a6..8559900b70 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -79,6 +79,7 @@ typedef enum
 	BgWorkerStart_PostmasterStart,
 	BgWorkerStart_ConsistentState,
 	BgWorkerStart_RecoveryFinished,
+	BgWorkerStart_ConsistentState_HotStandby,
 } BgWorkerStartTime;
 
 #define BGW_DEFAULT_RESTART_INTERVAL	60
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index bbd71d0b42..945d2608f6 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -22,6 +22,7 @@ extern void TablesyncWorkerMain(Datum main_arg);
 
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
+extern bool IsLogicalSlotSyncWorker(void);
 
 extern void HandleParallelApplyMessageInterrupt(void);
 extern void HandleParallelApplyMessages(void);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 5ddad69348..5a03f8c8bb 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -15,7 +15,6 @@
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "storage/spin.h"
-#include "replication/walreceiver.h"
 
 /*
  * Behaviour of replication slots, upon release or crash.
@@ -52,6 +51,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +119,13 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Is this a slot created by a sync-slot worker?
+	 *
+	 * Only relevant for logical slots on the physical standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)? Only
 	 * relevant for logical slots on the primary server.
@@ -225,9 +239,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover);
+								  bool two_phase, bool failover,
+								  char sync_state);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
@@ -253,7 +268,6 @@ extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_l
 extern int	ReplicationSlotIndex(ReplicationSlot *slot);
 extern bool ReplicationSlotName(int index, Name name);
 extern void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot);
-extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok);
 
 extern void StartupReplicationSlots(void);
 extern void CheckPointReplicationSlots(bool is_shutdown);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index f1135762fb..c96a814b26 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -19,6 +19,7 @@
 #include "pgtime.h"
 #include "port/atomics.h"
 #include "replication/logicalproto.h"
+#include "replication/slot.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/latch.h"
@@ -279,6 +280,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -403,6 +419,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -428,6 +445,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 84bb79ac0f..9406a2666f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -237,6 +237,11 @@ extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
+/* Slot sync worker objects */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+extern PGDLLIMPORT bool enable_syncslot;
+
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
@@ -326,6 +331,12 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
+extern void SlotSyncWorkerRegister(void);
+extern void ShutDownSlotSync(void);
+extern void slotsync_drop_initiated_slots(void);
+extern void SlotSyncWorkerShmemInit(void);
+
 #define isParallelApplyWorker(worker) ((worker)->in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
 #define isTablesyncWorker(worker) ((worker)->in_use && \
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
index bd6affaf48..20f9d825e7 100644
--- a/src/test/recovery/t/050_standby_failover_slots_sync.pl
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -296,4 +296,207 @@ is( $primary->safe_psql(
 
 $subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
 
+##################################################
+# Test logical failover slots on the standby
+# Configure standby3 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub3_slot->| ----> subscriber3 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb3_slot--->| ----> standby3 (connected via streaming replication)
+#                                        |                 lsub3_slot(synced_slot)
+##################################################
+
+# Cleanup old standby_slot_names
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = ''
+));
+$primary->start;
+
+$primary->psql('postgres',
+		q{SELECT pg_create_physical_replication_slot('sb3_slot');});
+
+# Create table and publication on primary
+$primary->safe_psql('postgres', "CREATE TABLE tab_mypub3 (a int PRIMARY KEY);");
+$primary->safe_psql('postgres', "CREATE PUBLICATION mypub3 FOR TABLE tab_mypub3;");
+
+$backup_name = 'backup3';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+my $connstr_1 = $primary->connstr;
+$standby3->stop;
+$standby3->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb3_slot'
+});
+$standby3->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+$standby3->start;
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb3_slot'
+));
+$primary->start;
+
+# Restart the standby
+$standby3->restart;
+my $standby3_conninfo = $standby3->connstr . ' dbname=postgres';
+
+# Create a subscriber node
+my $subscriber3 = PostgreSQL::Test::Cluster->new('subscriber3');
+$subscriber3->init(allows_streaming => 'logical');
+$subscriber3->start;
+$subscriber3->safe_psql('postgres', "CREATE TABLE tab_mypub3 (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true & wait for sync to complete.
+$subscriber3->safe_psql('postgres',
+		"CREATE SUBSCRIPTION mysub3 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION mypub3 WITH (slot_name = lsub3_slot, failover = true);");
+$subscriber3->wait_for_subscription_sync;
+
+# Wait for the standby to start sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub3_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub3_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby and is sync ready
+is($standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub3_slot';}),
+	"t|r",
+	'logical slot has failover as true and sync_state as ready on standby');
+
+##################################################
+# Test to confirm that restart_lsn and confirmed_flush_lsn of the logical slot
+# on the primary is synced to the standby
+##################################################
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_mypub3;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_mypub3 SELECT generate_series(1, 10);");
+
+# Get the restart_lsn for the logical slot lsub3_slot on the primary
+my $primary_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';");
+
+# Confirm that restart_lsn of lsub3_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';]);
+is($result, 't', 'restart_lsn of slot lsub3_slot synced to standby');
+
+# Get the confirmed_flush_lsn for the logical slot lsub3_slot on the primary
+$primary_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';");
+
+# Confirm that confirmed_flush_lsn of lsub3_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub3_slot synced to the standby');
+
+##################################################
+# Test that synchronized slot can neither be docoded nor dropped by the user
+##################################################
+
+# Disable hot_standby_feedback
+$standby3->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = off;');
+$standby3->restart;
+
+# Dropping of synced slot should result in error
+my ($result1, $stdout, $stderr) = $standby3->psql('postgres',
+	"SELECT pg_drop_replication_slot('lsub3_slot');");
+ok($stderr =~ /ERROR:  cannot drop replication slot "lsub3_slot"/,
+	"synced slot on standby cannot be dropped");
+
+# Logical decoding on synced slot should result in error
+($result1, $stdout, $stderr) = $standby3->psql('postgres',
+	"select * from pg_logical_slot_get_changes('lsub3_slot',NULL,NULL);");
+ok($stderr =~ /ERROR:  cannot use replication slot "lsub3_slot" for logical decoding/,
+	"logical decoding is not allowed on synced slot");
+
+# Enable hot_standby_feedback and restart standby
+$standby3->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = on;');
+$standby3->restart;
+
+##################################################
+# Create another slot which stays in sync_state as initiated ('i')
+##################################################
+
+# Create a logical slot with failover = true
+$primary->psql('postgres',
+		q{SELECT pg_create_logical_replication_slot('logical_slot','pgoutput', false, true, true);});
+
+# Wait for the standby to start sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"logical_slot\"/,
+	$offset);
+
+# Confirm that the logical slot is created on the standby and is in sync initiated state
+  is($standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'logical_slot';}),
+	"t|i",
+	'logical slot has failover as true and sync_state as initiated on standby');
+
+##################################################
+# Promote the standby3 to primary. Confirm that:
+# a) the sync-ready('r') slot 'lsub3_slot' is retained on new primary
+# b) the initiated('i') slot 'logical_slot'is dropped on promotion
+# c) logical replication for mysub3 is resumed succesfully after failover
+##################################################
+
+$standby3->promote;
+
+# Update subscription with new primary's connection info
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 DISABLE;");
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 CONNECTION '$standby3_conninfo';");
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 ENABLE;");
+
+is($standby3->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots WHERE slot_name in ('logical_slot','lsub3_slot');}),
+	'lsub3_slot',
+	'synced slot retained on new primary');
+
+# Insert data on the new primary
+$standby3->safe_psql('postgres',
+	"INSERT INTO tab_mypub3 SELECT generate_series(11, 20);");
+
+# Confirm that data in tab_mypub3 replicated on subscriber
+is( $subscriber3->safe_psql('postgres', q{SELECT count(*) FROM tab_mypub3;}),
+	"20",
+	'data replicated from new primary');
+
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 199863c6b5..10c6f32a30 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2321,6 +2321,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2579,6 +2580,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorkerCtx
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-14 01:30                                                       ` Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Peter Smith @ 2023-12-14 01:30 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi, here are a few more review comments for the patch v47-0002

(plus my review comments of v45-0002 [1] are yet to be addressed)

======
1. General

For consistency and readability, try to use variables of the same
names whenever they have the same purpose, even when they declared are
in different functions. A few like this were already mentioned in the
previous review but there are more I keep noticing.

For example,
'slotnameChanged' in function, VERSUS 'primary_slot_changed' in the caller.


======
src/backend/replication/logical/slotsync.c

2.
+/*
+ *
+ * Validates the primary server info.
+ *
+ * Using the specified primary server connection, it verifies whether
the master
+ * is a standby itself and returns true in that case to convey the caller that
+ * we are on the cascading standby.
+ * But if master is the primary server, it goes ahead and validates
+ * primary_slot_name. It emits error if the physical slot in primary_slot_name
+ * does not exist on the primary server.
+ */
+static bool
+validate_primary_info(WalReceiverConn *wrconn)

2a.
Extra line top of that comment?

~

2b.
IMO it is too tricky to have a function called "validate_xxx", when
actually you gave that return value some special unintuitive meaning
other than just validation. IMO it is always better for the returned
value to properly match the function name so the expectations are very
obvious. So, In this case, I think a better function signature would
be like this:

SUGGESTION

static void
validate_primary_info(WalReceiverConn *wrconn, bool *master_is_standby)

or

static void
validate_primary_info(WalReceiverConn *wrconn, bool *am_cascading_standby)

~~~

3.
+ if (res->status != WALRCV_OK_TUPLES)
+ ereport(ERROR,
+ (errmsg("could not fetch recovery and primary_slot_name \"%s\" info from the "
+ "primary: %s", PrimarySlotName, res->err)));

I'm not sure that including "recovery and" in the error message is
meaningful to the user, is it?

~~~

4. slotsync_reread_config

+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs have changed, re-validate them. The
+ * worker will exit if the check fails.
+ *
+ * Returns TRUE if primary_slot_name is changed, let the caller re-verify it.
+ */
+static bool
+slotsync_reread_config(WalReceiverConn *wrconn)

Hm. This is another function where the return value has been butchered
to have a special meaning unrelated the the function name. IMO it
makes the code unnecessarily confusing.

IMO a better function signature here would be:

static void
slotsync_reread_config(WalReceiverConn *wrconn, bool *primary_slot_name_changed)

~~~

5. ProcessSlotSyncInterrupts

+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static bool
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn, bool
check_cascading_standby)
+{

There is no function comment describing the meaning of the return
value. But actually, IMO this is an example of how conflating the
meanings of validation VERSUS are_we_cascading_standby in the
lower-down function has propagated up to become a big muddle.

The code
+ if (primary_slot_changed || check_cascading_standby)
+ return validate_primary_info(wrconn);

seems unnecessarily hard to understand because,
false -- doesn't mean invalid
true -- doesn't mean valid

Please, consider changing this signature also so the functions return
what you would intuitively expect them to return without surprisingly
different meanings.

SUGGESTION

static void
ProcessSlotSyncInterrupts(WalReceiverConn *wrconn, bool
check_cascading_standby, bool *am_cascading_standby)

~~~

6. ReplSlotSyncWorkerMain

+ int rc;
+ long naptime = WORKER_DEFAULT_NAPTIME_MS;
+ TimestampTz now;
+ bool slot_updated;
+
+ /*
+ * The transaction env is needed by walrcv_exec() in both the slot
+ * sync and primary info validation flow.
+ */
+ StartTransactionCommand();
+
+ if (!am_cascading_standby)
+ {
+ slot_updated = synchronize_slots(wrconn);
+
+ /*
+ * If any of the slots get updated in this sync-cycle, use default
+ * naptime and update 'last_update_time'. But if no activity is
+ * observed in this sync-cycle, then increase naptime provided
+ * inactivity time reaches threshold.
+ */
+ now = GetCurrentTimestamp();
+ if (slot_updated)
+ last_update_time = now;
+ else if (TimestampDifferenceExceeds(last_update_time,
+ now, WORKER_INACTIVITY_THRESHOLD_MS))
+ naptime = WORKER_INACTIVITY_NAPTIME_MS;
+ }
+ else
+ naptime = 6 * WORKER_INACTIVITY_NAPTIME_MS; /* 60 sec */
+
+ rc = WaitLatch(MyLatch,
+    WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+    naptime,
+    WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+ if (rc & WL_LATCH_SET)
+ ResetLatch(MyLatch);
+
+ am_cascading_standby =
+ ProcessSlotSyncInterrupts(wrconn, am_cascading_standby);
+
+ CommitTransactionCommand();

IMO it is more natural to avoid negative conditions, so just reverse
these. Also, some comment is needed to explain why the longer naptime
is needed in this special case.


SUGGESTION
if (am_cascading_standby)
{
  /* comment the reason .... */
  naptime = 6 * WORKER_INACTIVITY_NAPTIME_MS; /* 60 sec */
}
else
{
  /* Normal standby */
  ...
}

======
[1] review of v45-0002.
https://www.postgresql.org/message-id/CAHut%2BPtOc7J_n24HJ6f_dFWTuD3X2ApOByQzZf6jZz%2B0wb-ebQ%40mail...

Kind Regards,
Peter Smith.
Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
@ 2023-12-14 11:10                                                         ` Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Amit Kapila @ 2023-12-14 11:10 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; +Cc: shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Thu, Dec 14, 2023 at 7:00 AM Peter Smith <[email protected]> wrote:
>
> Hi, here are a few more review comments for the patch v47-0002
>
> (plus my review comments of v45-0002 [1] are yet to be addressed)
>
> ======
> 1. General
>
> For consistency and readability, try to use variables of the same
> names whenever they have the same purpose, even when they declared are
> in different functions. A few like this were already mentioned in the
> previous review but there are more I keep noticing.
>
> For example,
> 'slotnameChanged' in function, VERSUS 'primary_slot_changed' in the caller.
>
>
> ======
> src/backend/replication/logical/slotsync.c
>
> 2.
> +/*
> + *
> + * Validates the primary server info.
> + *
> + * Using the specified primary server connection, it verifies whether
> the master
> + * is a standby itself and returns true in that case to convey the caller that
> + * we are on the cascading standby.
> + * But if master is the primary server, it goes ahead and validates
> + * primary_slot_name. It emits error if the physical slot in primary_slot_name
> + * does not exist on the primary server.
> + */
> +static bool
> +validate_primary_info(WalReceiverConn *wrconn)
>
> 2b.
> IMO it is too tricky to have a function called "validate_xxx", when
> actually you gave that return value some special unintuitive meaning
> other than just validation. IMO it is always better for the returned
> value to properly match the function name so the expectations are very
> obvious. So, In this case, I think a better function signature would
> be like this:
>
> SUGGESTION
>
> static void
> validate_primary_info(WalReceiverConn *wrconn, bool *master_is_standby)
>
> or
>
> static void
> validate_primary_info(WalReceiverConn *wrconn, bool *am_cascading_standby)
>

The terminology master_is_standby is a bit indirect for this usage, so
I would prefer the second one. Shall we name this function as
check_primary_info()? Additionally, can we rewrite the following
comment: "Using the specified primary server connection, check whether
we are cascading standby. It also validates primary_slot_info for
non-cascading-standbys.".

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-12-15 05:32                                                           ` shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-15 05:32 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; +Cc: Peter Smith <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Thu, Dec 14, 2023 at 4:40 PM Amit Kapila <[email protected]> wrote:
>
> On Thu, Dec 14, 2023 at 7:00 AM Peter Smith <[email protected]> wrote:
> >
> > Hi, here are a few more review comments for the patch v47-0002
> >
> > (plus my review comments of v45-0002 [1] are yet to be addressed)
> >
> > ======
> > 1. General
> >
> > For consistency and readability, try to use variables of the same
> > names whenever they have the same purpose, even when they declared are
> > in different functions. A few like this were already mentioned in the
> > previous review but there are more I keep noticing.
> >
> > For example,
> > 'slotnameChanged' in function, VERSUS 'primary_slot_changed' in the caller.
> >
> >
> > ======
> > src/backend/replication/logical/slotsync.c
> >
> > 2.
> > +/*
> > + *
> > + * Validates the primary server info.
> > + *
> > + * Using the specified primary server connection, it verifies whether
> > the master
> > + * is a standby itself and returns true in that case to convey the caller that
> > + * we are on the cascading standby.
> > + * But if master is the primary server, it goes ahead and validates
> > + * primary_slot_name. It emits error if the physical slot in primary_slot_name
> > + * does not exist on the primary server.
> > + */
> > +static bool
> > +validate_primary_info(WalReceiverConn *wrconn)
> >
> > 2b.
> > IMO it is too tricky to have a function called "validate_xxx", when
> > actually you gave that return value some special unintuitive meaning
> > other than just validation. IMO it is always better for the returned
> > value to properly match the function name so the expectations are very
> > obvious. So, In this case, I think a better function signature would
> > be like this:
> >
> > SUGGESTION
> >
> > static void
> > validate_primary_info(WalReceiverConn *wrconn, bool *master_is_standby)
> >
> > or
> >
> > static void
> > validate_primary_info(WalReceiverConn *wrconn, bool *am_cascading_standby)
> >
>
> The terminology master_is_standby is a bit indirect for this usage, so
> I would prefer the second one. Shall we name this function as
> check_primary_info()? Additionally, can we rewrite the following
> comment: "Using the specified primary server connection, check whether
> we are cascading standby. It also validates primary_slot_info for
> non-cascading-standbys.".
>
> --
> With Regards,
> Amit Kapila.


PFA v48. Changes are:

1) Addressed comments by Peter for v45-002 and v47-002 given in [1]
and [2] respectively
2) Addressed comments by Amit for v47-002 given in [3], [4]
3) Addressed an old comment (#74 in [5]) of getting rid of header
inclusion from tablesync.c when there was no code change in that file.
Thanks Hou-san for working on this change.


TODO:
--Address the test comments in [1] for 050_standby_failover_slots_sync.pl
--Review the feasibility of addressing one pending comment (comment 13
in [5]) of 'r'->'n' conversion.

[1]: https://www.postgresql.org/message-id/CAHut%2BPtOc7J_n24HJ6f_dFWTuD3X2ApOByQzZf6jZz%2B0wb-ebQ%40mail...
[2]: https://www.postgresql.org/message-id/CAHut%2BPsvxs-%3Dj3aCpPVs3e4w78HndCdO-F4bLPzAX70%2BdgWUuQ%40ma...
[3]: https://www.postgresql.org/message-id/CAA4eK1L2ts%3DgfiF4aw7-DH8HWj29s08hVRq-Ff8%3DmjfdUXx8CA%40mail...
[4]: https://www.postgresql.org/message-id/CAA4eK1%2Bw9yv%2B4UZXhiDHZpGDfbeRHYDBu23FwsniS8sYUZeu1w%40mail...
[5]: https://www.postgresql.org/message-id/CAJpy0uDcOf5Hvk_CdCCAbfx9SY%2Bog%3D%3D%3DtgiuhWKzkYyqebui9g%40...

thanks
Shveta






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-15 05:33                                                             ` shveta malik <[email protected]>
  2023-12-18 10:52                                                               ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-18 23:20                                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  0 siblings, 2 replies; 113+ messages in thread

From: shveta malik @ 2023-12-15 05:33 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; +Cc: Peter Smith <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Fri, Dec 15, 2023 at 11:02 AM shveta malik <[email protected]> wrote:
>
> On Thu, Dec 14, 2023 at 4:40 PM Amit Kapila <[email protected]> wrote:
> >
> > On Thu, Dec 14, 2023 at 7:00 AM Peter Smith <[email protected]> wrote:
> > >
> > > Hi, here are a few more review comments for the patch v47-0002
> > >
> > > (plus my review comments of v45-0002 [1] are yet to be addressed)
> > >
> > > ======
> > > 1. General
> > >
> > > For consistency and readability, try to use variables of the same
> > > names whenever they have the same purpose, even when they declared are
> > > in different functions. A few like this were already mentioned in the
> > > previous review but there are more I keep noticing.
> > >
> > > For example,
> > > 'slotnameChanged' in function, VERSUS 'primary_slot_changed' in the caller.
> > >
> > >
> > > ======
> > > src/backend/replication/logical/slotsync.c
> > >
> > > 2.
> > > +/*
> > > + *
> > > + * Validates the primary server info.
> > > + *
> > > + * Using the specified primary server connection, it verifies whether
> > > the master
> > > + * is a standby itself and returns true in that case to convey the caller that
> > > + * we are on the cascading standby.
> > > + * But if master is the primary server, it goes ahead and validates
> > > + * primary_slot_name. It emits error if the physical slot in primary_slot_name
> > > + * does not exist on the primary server.
> > > + */
> > > +static bool
> > > +validate_primary_info(WalReceiverConn *wrconn)
> > >
> > > 2b.
> > > IMO it is too tricky to have a function called "validate_xxx", when
> > > actually you gave that return value some special unintuitive meaning
> > > other than just validation. IMO it is always better for the returned
> > > value to properly match the function name so the expectations are very
> > > obvious. So, In this case, I think a better function signature would
> > > be like this:
> > >
> > > SUGGESTION
> > >
> > > static void
> > > validate_primary_info(WalReceiverConn *wrconn, bool *master_is_standby)
> > >
> > > or
> > >
> > > static void
> > > validate_primary_info(WalReceiverConn *wrconn, bool *am_cascading_standby)
> > >
> >
> > The terminology master_is_standby is a bit indirect for this usage, so
> > I would prefer the second one. Shall we name this function as
> > check_primary_info()? Additionally, can we rewrite the following
> > comment: "Using the specified primary server connection, check whether
> > we are cascading standby. It also validates primary_slot_info for
> > non-cascading-standbys.".
> >
> > --
> > With Regards,
> > Amit Kapila.
>
>
> PFA v48. Changes are:
>
Sorry, I missed attaching the patch. PFA v48.

> 1) Addressed comments by Peter for v45-002 and v47-002 given in [1]
> and [2] respectively
> 2) Addressed comments by Amit for v47-002 given in [3], [4]
> 3) Addressed an old comment (#74 in [5]) of getting rid of header
> inclusion from tablesync.c when there was no code change in that file.
> Thanks Hou-san for working on this change.
>
>
> TODO:
> --Address the test comments in [1] for 050_standby_failover_slots_sync.pl
> --Review the feasibility of addressing one pending comment (comment 13
> in [5]) of 'r'->'n' conversion.
>
> [1]: https://www.postgresql.org/message-id/CAHut%2BPtOc7J_n24HJ6f_dFWTuD3X2ApOByQzZf6jZz%2B0wb-ebQ%40mail...
> [2]: https://www.postgresql.org/message-id/CAHut%2BPsvxs-%3Dj3aCpPVs3e4w78HndCdO-F4bLPzAX70%2BdgWUuQ%40ma...
> [3]: https://www.postgresql.org/message-id/CAA4eK1L2ts%3DgfiF4aw7-DH8HWj29s08hVRq-Ff8%3DmjfdUXx8CA%40mail...
> [4]: https://www.postgresql.org/message-id/CAA4eK1%2Bw9yv%2B4UZXhiDHZpGDfbeRHYDBu23FwsniS8sYUZeu1w%40mail...
> [5]: https://www.postgresql.org/message-id/CAJpy0uDcOf5Hvk_CdCCAbfx9SY%2Bog%3D%3D%3DtgiuhWKzkYyqebui9g%40...
>
> thanks
> Shveta


Attachments:

  [application/octet-stream] v48-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (146.6K, ../../CAJpy0uD+FcaakEpUp3CVAn+PhmRdkxb8gv4ONZCgPQ7Jm0rBcg@mail.gmail.com/2-v48-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From 4ddd19b51ae0c9f0a3aff41a7630ec0ca48f5359 Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Wed, 6 Dec 2023 15:52:16 +0530
Subject: [PATCH v48 1/2] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to
the physical standbys so that logical replication can be resumed after
failover. It is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API.

Ex1:
CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

Ex2: (failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

A new replication command called ALTER_REPLICATION_SLOT and a
corresponding walreceiver API function named walrcv_alter_slot have been
implemented. They allow subscribers or users to modify the failover
property of a replication slot on the publisher.

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).
---
 contrib/test_decoding/expected/slot.out       |  58 +++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |  20 +-
 doc/src/sgml/ref/create_subscription.sgml     |  23 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       | 114 +++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  38 +-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  53 ++-
 src/backend/replication/logical/worker.c      |  67 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 192 +++++++++-
 src/backend/replication/slotfuncs.c           |  25 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 358 +++++++++++++++++-
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_dump/pg_dump.c                     |  20 +-
 src/bin/pg_dump/pg_dump.h                     |   1 +
 src/bin/pg_upgrade/info.c                     |   5 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 .../t/050_standby_failover_slots_sync.pl      | 299 +++++++++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 ++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 50 files changed, 1569 insertions(+), 170 deletions(-)
 create mode 100644 src/test/recovery/t/050_standby_failover_slots_sync.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 44cada2b40..7993fe3cdd 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4360,6 +4360,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..481e397bad 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
@@ -230,6 +233,17 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
+
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property of the new slot may differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription. When creating the slot,
+      ensure the slot failover property matches the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter value of the subscription.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..fa6cd1c43f 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,29 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..e68f0d401e 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,38 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If the slot_name is specified without the create_slot option,
+			 * it is possible that the user intends to use an existing slot on
+			 * the publisher, so here we alter the failover property of the
+			 * slot to match the failover value in subscription.
+			 *
+			 * We do not need to change the failover to false if the server
+			 * does not support failover (e.g. pre-PG17)
+			 */
+			else if (opts.slot_name &&
+					 (failover_enabled || walrcv_server_version(wrconn) >= 170000))
+			{
+				bool		failover_delayed = (!failover_enabled && opts.failover);
+
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+								opts.slot_name, failover_enabled ? "true" : "false"),
+						 failover_delayed ?
+						 errdetail("The failover state will be set to true once table synchronization has been completed.")
+						 : 0));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,13 +1335,22 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
@@ -1334,13 +1399,26 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 						/* translator: %s is an SQL ALTER command */
 								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
 										 isadd ?
@@ -1389,7 +1467,19 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 					ereport(ERROR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "two_phase"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "failover"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 693b3669ba..cf11b98da3 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -888,8 +892,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -918,7 +922,8 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+			appendStringInfoString(&cmd, "FAILOVER, ");
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -987,6 +992,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 4d056c16c8..7b6170fe55 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -624,15 +624,28 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "two_phase")));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "failover")));
+
 				should_exit = true;
 			}
 		}
@@ -1430,7 +1443,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1732,10 +1746,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1743,9 +1759,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1759,9 +1774,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..e46a1955e8 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,6 +132,33 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * failover = true when creating the subscription. Enabling failover allows us
+ * to smoothly transition to the promoted standby, ensuring that we can
+ * subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
@@ -3947,6 +3974,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4510,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4568,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4608,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..49d2de5024 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -248,10 +260,13 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     during getting changes, if the two_phase option is enabled it can skip
  *     prepare because by that time start decoding point has been moved. So the
  *     user will only get commit prepared.
+ * failover: If enabled, allows the slot to be synced to physical standbys so
+ *     that logical replication can be resumed after failover.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +695,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2200,148 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+	bool		ok;
+
+	/* Need a modifiable copy of string. */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into a list of identifiers. */
+	ok = SplitIdentifierString(rawname, ',', &elemlist);
+
+	if (!ok)
+		GUC_check_errdetail("List syntax is invalid.");
+
+	/*
+	 * If there is a syntax error in the name or if the replication slots'
+	 * data is not initialized yet (i.e., we are in the startup process), skip
+	 * the slot verification.
+	 */
+	if (!ok || !ReplicationSlotCtl)
+	{
+		pfree(rawname);
+		list_free(elemlist);
+		return ok;
+	}
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			GUC_check_errdetail("replication slot \"%s\" does not exist",
+								name);
+			ok = false;
+			break;
+		}
+
+		if (!SlotIsPhysical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			ok = false;
+			break;
+		}
+	}
+
+	pfree(rawname);
+	list_free(elemlist);
+	return ok;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	/*
+	 * Since we do not support syncing slots to cascading standbys, we return
+	 * NIL here if we are running in a standby to indicate that no standby
+	 * slots need to be waited for.
+	 */
+	if (RecoveryInProgress())
+		return NIL;
+
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..c87f61666d 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -491,6 +501,12 @@ pg_logical_replication_slot_advance(XLogRecPtr moveto)
 											   .segment_close = wal_segment_close),
 									NULL, NULL, NULL);
 
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to moveto lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(moveto);
+
 		/*
 		 * Start reading at the slot's restart_lsn, which we know to point to
 		 * a valid record.
@@ -679,6 +695,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +751,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +791,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 26ded928a7..ca61a99785 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -387,7 +387,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..fddc9310c6 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool		failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,257 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up the logical walsender processes with failover-enabled slots if the
+ * physical slot of the current walsender is specified in standby_slot_names
+ * GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names;
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	/*
+	 * If we are running on a standby, there is no need to reload
+	 * standby_slot_names since we do not support syncing slots to cascading
+	 * standbys.
+	 */
+	if (RecoveryInProgress())
+		return;
+
+	pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn. Additionally,
+ * it removes slots that have been invalidated, dropped, or converted to
+ * logical slots.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			/*
+			 * It may happen that the slot specified in standby_slot_names GUC
+			 * value is dropped, so let's skip over it.
+			 */
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+		}
+		else if (SlotIsLogical(slot))
+		{
+			/*
+			 * If a logical slot name is provided in standby_slot_names, issue
+			 * a WARNING and skip it. Although logical slots are disallowed in
+			 * the GUC check_hook(validate_standby_slots), it is still
+			 * possible for a user to drop an existing physical slot and
+			 * recreate a logical slot with the same name. Since it is
+			 * harmless, a WARNING should be enough, no need to error-out.
+			 */
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+		}
+		else
+		{
+			SpinLockAcquire(&slot->mutex);
+
+			if (slot->data.invalidated != RS_INVAL_NONE)
+			{
+				/*
+				 * Specified physical slot have been invalidated, so no point
+				 * in waiting for it.
+				 */
+				warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+			}
+			else if (XLogRecPtrIsInvalid(slot->data.restart_lsn) ||
+					 slot->data.restart_lsn < wait_for_lsn)
+			{
+				bool	inactive = (slot->active_pid == 0);
+
+				SpinLockRelease(&slot->mutex);
+
+				/* Log warning if no active_pid for this physical slot */
+				if (inactive)
+					ereport(WARNING,
+							errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+								   name, "standby_slot_names"),
+							errdetail("Logical replication is waiting on the "
+									  "standby associated with \"%s\".", name),
+							errhint("Consider starting standby associated with "
+									"\"%s\" or amend standby_slot_names.", name));
+
+				/* Continue if the current slot hasn't caught up. */
+				continue;
+			}
+			else
+			{
+				Assert(slot->data.restart_lsn >= wait_for_lsn);
+			}
+
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving the given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	if (standby_slots == NIL)
+		return;
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, we also
+ * wait for all the specified streaming replication standby servers to
+ * confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr even when we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
-	if (RecentFlushPtr != InvalidXLogRecPtr &&
-		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	if (!XLogRecPtrIsInvalid(RecentFlushPtr) && loc <= RecentFlushPtr)
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know
+		 * we have enough WAL available and all the standby servers have
+		 * confirmed receipt of WAL up to RecentFlushPtr. This is particularly
+		 * interesting if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1849,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1864,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,9 +1903,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
+		{
+			/* Already caught up and doesn't need to wait for standby_slots. */
 			break;
+		}
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
 		WalSndCaughtUp = true;
@@ -1654,9 +1954,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2121,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2358,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3621,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3692,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another
+	 * CV that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f7c9882f7c..9a54e04821 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4571,6 +4571,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..5d940b72cd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -329,6 +329,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8c0b5486b9..373c2514df 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4618,6 +4618,7 @@ getSubscriptions(Archive *fout)
 	int			i_subsynccommit;
 	int			i_subpublications;
 	int			i_suborigin;
+	int			i_subfailoverstate;
 	int			i,
 				ntups;
 
@@ -4673,14 +4674,22 @@ getSubscriptions(Archive *fout)
 		appendPQExpBufferStr(query,
 							 " s.subpasswordrequired,\n"
 							 " s.subrunasowner,\n"
-							 " s.suborigin\n");
+							 " s.suborigin,\n");
 	else
 		appendPQExpBuffer(query,
 						  " 't' AS subpasswordrequired,\n"
 						  " 't' AS subrunasowner,\n"
-						  " '%s' AS suborigin\n",
+						  " '%s' AS suborigin,\n",
 						  LOGICALREP_ORIGIN_ANY);
 
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(query,
+							 " s.subfailoverstate\n");
+	else
+		appendPQExpBuffer(query,
+						  " '%c' AS subfailoverstate\n",
+						  LOGICALREP_FAILOVER_STATE_DISABLED);
+
 	appendPQExpBufferStr(query,
 						 "FROM pg_subscription s\n"
 						 "WHERE s.subdbid = (SELECT oid FROM pg_database\n"
@@ -4709,6 +4718,7 @@ getSubscriptions(Archive *fout)
 	i_subsynccommit = PQfnumber(res, "subsynccommit");
 	i_subpublications = PQfnumber(res, "subpublications");
 	i_suborigin = PQfnumber(res, "suborigin");
+	i_subfailoverstate = PQfnumber(res, "subfailoverstate");
 
 	subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo));
 
@@ -4746,6 +4756,8 @@ getSubscriptions(Archive *fout)
 		subinfo[i].subpublications =
 			pg_strdup(PQgetvalue(res, i, i_subpublications));
 		subinfo[i].suborigin = pg_strdup(PQgetvalue(res, i, i_suborigin));
+		subinfo[i].subfailoverstate =
+			pg_strdup(PQgetvalue(res, i, i_subfailoverstate));
 
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(subinfo[i].dobj), fout);
@@ -4771,6 +4783,7 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	int			npubnames = 0;
 	int			i;
 	char		two_phase_disabled[] = {LOGICALREP_TWOPHASE_STATE_DISABLED, '\0'};
+	char		failover_disabled[] = {LOGICALREP_FAILOVER_STATE_DISABLED, '\0'};
 
 	/* Do nothing in data-only dump */
 	if (dopt->dataOnly)
@@ -4818,6 +4831,9 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	if (strcmp(subinfo->subtwophasestate, two_phase_disabled) != 0)
 		appendPQExpBufferStr(query, ", two_phase = on");
 
+	if (strcmp(subinfo->subfailoverstate, failover_disabled) != 0)
+		appendPQExpBufferStr(query, ", failover = true");
+
 	if (strcmp(subinfo->subdisableonerr, "t") == 0)
 		appendPQExpBufferStr(query, ", disable_on_error = true");
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2fe3cbed9a..f62a4dfd4b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -671,6 +671,7 @@ typedef struct _SubscriptionInfo
 	char	   *subsynccommit;
 	char	   *subpublications;
 	char	   *suborigin;
+	char	   *subfailoverstate;
 } SubscriptionInfo;
 
 /*
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..e16286f18c 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,6 +679,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
@@ -687,6 +688,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +699,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..2d8bcb26f9 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced to the
+								 * physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 020e7aa1cc..cb3a2b3aed 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -159,7 +159,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -179,8 +179,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 049801186c..905964a2e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3327,7 +3327,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 77e8b13764..bbc03bb76b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..5ddad69348 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)? Only
+	 * relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 949e874f21..f1135762fb 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -355,9 +355,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									  const char *slotname,
+									  bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -399,6 +410,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -428,8 +440,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 60313980a9..ecd212c7b6 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -12,6 +12,8 @@
 #ifndef _WALSENDER_H
 #define _WALSENDER_H
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -45,6 +47,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index db73408937..84bb79ac0f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -256,7 +256,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..083b558448 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_standby_failover_slots_sync.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
new file mode 100644
index 0000000000..bd6affaf48
--- /dev/null
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -0,0 +1,299 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+##################################################
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+##################################################
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on the primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres',
+	"CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+
+# Create a table and a subscription with failover = true
+$subscriber1->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);
+]);
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	CREATE SUBSCRIPTION regress_mysub2 CONNECTION '$publisher_connstr' PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);
+]);
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with the specified physical replication slot so
+# that the logical replication slot won't receive changes until the standby
+# comes up.
+$standby1->stop;
+
+# Create some data on the primary
+my $primary_row_count   = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('regress_mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary until standby1 acknowledges changes"
+);
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 acknowledges changes");
+
+# Stop the standby associated with the specified physical replication slot so
+# that the logical replication slot won't receive changes until the standby
+# slot's restart_lsn is advanced or the slot is removed from the
+# standby_slot_names list
+$publisher->safe_psql('postgres', "TRUNCATE tab_int;");
+$publisher->wait_for_catchup('regress_mysub1');
+$standby1->stop;
+
+##################################################
+# Verify that when using pg_logical_slot_get_changes to consume changes from a
+# logical slot with failover enabled, it will also wait for the slots specified
+# in standby_slot_names to catch up.
+##################################################
+
+# Create a logical 'test_decoding' replication slot with failover enabled
+$publisher->safe_psql('postgres',
+	"SELECT pg_create_logical_replication_slot('test_slot', 'test_decoding', false, false, true);"
+);
+
+my $back_q = $primary->background_psql('postgres', on_error_stop => 0);
+my $pid = $back_q->query('SELECT pg_backend_pid()');
+
+# Try and get changes from the logical slot with failover enabled.
+my $offset = -s $primary->logfile;
+$back_q->query_until(qr//,
+	"SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL);\n");
+
+# Wait until the primary server logs a warning indicating that it is waiting
+# for the sb1_slot to catch up.
+$primary->wait_for_log(
+	qr/WARNING: ( [A-Z0-9]+:)? replication slot \"sb1_slot\" specified in parameter \"standby_slot_names\" does not have active_pid/,
+	$offset);
+
+ok($primary->safe_psql('postgres', "SELECT pg_cancel_backend($pid)"),
+	"cancelling pg_logical_slot_get_changes command");
+
+$publisher->safe_psql('postgres',
+	"SELECT pg_drop_replication_slot('test_slot');"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we manually advance this slot's LSN to the
+# latest position.
+##################################################
+
+# Create some data on the primary
+$primary_row_count = 10;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# The subscription that's up and running and is enabled for failover doesn't
+# get the data from primary and keeps waiting for the standby specified in
+# standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary as long as standby1 restart_lsn has not been updated"
+);
+
+# Advance the lsn of the standby slot manually
+$primary->safe_psql('postgres',
+	"SELECT * FROM pg_replication_slot_advance('sb1_slot', pg_current_wal_lsn());"
+);
+
+# Now that the standby lsn has advanced, primary must send the decoded
+# changes to the subscription
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1's restart_lsn has been updated"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we remove the slot from standby_slot_names.
+##################################################
+
+# Create some data on the primary
+$primary_row_count = 20;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(11,20);");
+
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 10 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data as the sb1_slot doesn't catch up");
+
+# Remove the standby from the standby_slot_names list and reload the
+# configuration
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', "''");
+$primary->psql('postgres', "SELECT pg_reload_conf()");
+
+# Now that the standby lsn has advanced, the primary must send the decoded
+# changes to the subscription.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 is removed from the standby_slot_names list"
+);
+
+# Put the standby back on the primary_slot_name for the rest of the tests
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', 'sb1_slot');
+$primary->restart();
+
+##################################################
+# Test that when a subscription with failover enabled is created, it will alter
+# the failover property of the corresponding slot on the publisher.
+##################################################
+
+# Create a slot on the publisher with failover disabled
+$primary->safe_psql('postgres',
+	"SELECT 'init' FROM pg_create_logical_replication_slot('lsub3_slot', 'pgoutput', false, false, false);"
+);
+
+# Confirm that the failover flag on the slot is turned off
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"f",
+	'logical slot has failover false on the primary');
+
+# Create another subscription (using the same slot created above) that enables
+# failover.
+$subscriber1->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub3 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub3_slot, copy_data=false, failover = true, create_slot = false);"
+);
+
+# Confirm that the failover flag on the slot has now been turned on
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"t",
+	'logical slot has failover true on the primary');
+
+$subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ba41149b88..199863c6b5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3870,6 +3871,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.34.1



  [application/octet-stream] v48-0002-Add-logical-slot-sync-capability-to-the-physical.patch (90.0K, ../../CAJpy0uD+FcaakEpUp3CVAn+PhmRdkxb8gv4ONZCgPQ7Jm0rBcg@mail.gmail.com/3-v48-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From 3d549cb45a3a5cc87675efcd3352e658a56e47e9 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Tue, 12 Dec 2023 16:55:06 +0800
Subject: [PATCH v48 2/2] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover. All the failover logical
replication slots on the primary (assuming configurations are
appropriate) are automatically created on the physical standbys and
are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The nap time of the worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. If
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated.

If a logical slot on the primary is valid but is invalidated on the standby,
then that slot is dropped and recreated on the standby in next sync-cycle. It
is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently). This situation may occur due to the
following reasons:
- The max_slot_wal_keep_size on the standby is insufficient to retain WAL
  records from the restart_lsn of the slot.
- primary_slot_name is temporarily reset to null and the physical slot is
  removed.
- The primary changes wal_level to a level lower than logical.

The slots synchronization status on the standby can be monitored using
'sync_state' column of pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but slot is not ready yet for periodic syncs,
'r': ready for periodic syncs.
---
 doc/src/sgml/bgworker.sgml                    |   65 +-
 doc/src/sgml/config.sgml                      |   32 +-
 doc/src/sgml/logicaldecoding.sgml             |   29 +
 doc/src/sgml/system-views.sgml                |   35 +
 src/backend/access/transam/xlogrecovery.c     |   10 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    4 +
 src/backend/postmaster/postmaster.c           |   10 +
 .../libpqwalreceiver/libpqwalreceiver.c       |   41 +
 src/backend/replication/logical/Makefile      |    1 +
 src/backend/replication/logical/logical.c     |   25 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1319 +++++++++++++++++
 src/backend/replication/logical/worker.c      |   15 +-
 src/backend/replication/slot.c                |   26 +-
 src/backend/replication/slotfuncs.c           |   37 +-
 src/backend/replication/walsender.c           |    6 +-
 src/backend/storage/ipc/ipci.c                |    2 +
 src/backend/tcop/postgres.c                   |   11 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   10 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/commands/subscriptioncmds.h       |    1 +
 src/include/postmaster/bgworker.h             |    1 +
 src/include/replication/logicalworker.h       |    1 +
 src/include/replication/slot.h                |   22 +-
 src/include/replication/walreceiver.h         |   18 +
 src/include/replication/worker_internal.h     |   11 +
 .../t/050_standby_failover_slots_sync.pl      |  203 +++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    2 +
 33 files changed, 1929 insertions(+), 33 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2c393385a9..a7cfe6c58c 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -114,18 +114,59 @@ typedef struct BackgroundWorker
 
   <para>
    <structfield>bgw_start_time</structfield> is the server state during which
-   <command>postgres</command> should start the process; it can be one of
-   <literal>BgWorkerStart_PostmasterStart</literal> (start as soon as
-   <command>postgres</command> itself has finished its own initialization; processes
-   requesting this are not eligible for database connections),
-   <literal>BgWorkerStart_ConsistentState</literal> (start as soon as a consistent state
-   has been reached in a hot standby, allowing processes to connect to
-   databases and run read-only queries), and
-   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system has
-   entered normal read-write state).  Note the last two values are equivalent
-   in a server that's not a hot standby.  Note that this setting only indicates
-   when the processes are to be started; they do not stop when a different state
-   is reached.
+   <command>postgres</command> should start the process. Note that this setting
+   only indicates when the processes are to be started; they do not stop when
+   a different state is reached. Possible values are:
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>BgWorkerStart_PostmasterStart</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_PostmasterStart</primary></indexterm>
+       Start as soon as postgres itself has finished its own initialization;
+       processes requesting this are not eligible for database connections.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState</primary></indexterm>
+       Start as soon as a consistent state has been reached in a hot-standby,
+       allowing processes to connect to databases and run read-only queries.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState_HotStandby</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState_HotStandby</primary></indexterm>
+       Same meaning as <literal>BgWorkerStart_ConsistentState</literal> but
+       it is more strict in terms of the server i.e. start the worker only
+       if it is hot-standby.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_RecoveryFinished</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_RecoveryFinished</primary></indexterm>
+       Start as soon as the system has entered normal read-write state. Note
+       that the <literal>BgWorkerStart_ConsistentState</literal> and
+      <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
+       in a server that's not a hot standby.
+      </para>
+     </listitem>
+    </varlistentry>
+
+   </variablelist>
   </para>
 
   <para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 7993fe3cdd..f98e3e6ab6 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4373,6 +4373,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must configure
+        <literal>enable_syncslot = true</literal> so they can receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4568,8 +4574,12 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           <varname>primary_conninfo</varname> string, or in a separate
           <filename>~/.pgpass</filename> file on the standby server (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          If slot synchronization is enabled then it is also necessary to
+          specify <literal>dbname</literal> in the
+          <varname>primary_conninfo</varname> string. This will only be used for
+          slot synchronization. It is ignored for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4894,6 +4904,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..c686175098 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,35 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and setting
+     <xref linkend="guc-enable-syncslot"/> on the standby. For the synchronization
+     to work, it is mandatory to have a physical replication slot between the
+     primary and the standby, and <varname>hot_standby_feedback</varname> must
+     be enabled on the standby. It's also highly recommended that the said
+     physical replication slot is named in <varname>standby_slot_names</varname>
+     list on the primary, to prevent the subscriber from consuming changes
+     faster than the hot standby.
+    </para>
+
+    <para>
+     The ability to resume logical replication after failover depends upon the
+     <link linkend="view-pg-replication-slots">pg_replication_slots</link>.<structfield>sync_state</structfield>
+     value for the synchronized slots on the standby at the time of failover.
+     Only slots that have attained "ready" sync_state ('r') on the standby
+     before failover can be used for logical replication after failover. Slots
+     that have not yet reached 'r' state (they are still 'i') will be dropped,
+     therefore logical replication for those slots cannot be resumed. For
+     example, if the synchronized slot could not become sync-ready on standby
+     due to a disabled subscription, then the subscription cannot be resumed
+     after failover even when it is enabled.
+     If the primary is idle, then the synchronized slots on the standby may
+     take a noticeable time to reach the ready ('r') sync_state. This can
+     be sped up by calling the
+     <function>pg_log_standby_snapshot</function> function on the primary.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..9847342601 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,41 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has configured <xref linkend="guc-enable-syncslot"/> = true.
+      Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para><literal>n</literal> = none for user created slots,
+         </para>
+        </listitem>
+        <listitem>
+         <para><literal>i</literal> = sync initiated for the slot but slot
+         is not ready yet for periodic syncs,
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>r</literal> = ready for periodic syncs.
+         </para>
+        </listitem>
+       </itemizedlist>
+      </para>
+      <para>
+      The hot standby can have any of these sync_state values for the slots but
+      on a hot standby, the slots with state 'r' and 'i' can neither be used
+      for logical decoding nor dropped by the user.
+      The sync_state has no meaning on the primary server; the primary
+      sync_state value is default 'n' for all slots but may (if leftover
+      from a promoted standby)  also be 'r'.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index a2c8fa3981..c867cfbc63 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1436,15 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 3c99cf6047..7f74f53ad1 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -21,6 +21,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "replication/worker_internal.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -129,6 +130,9 @@ static const struct
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 651b85ea74..9b74a49663 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -115,6 +115,7 @@
 #include "postmaster/syslogger.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -1003,6 +1004,12 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	/*
+	 * Register the slot sync worker here to kick start slot-sync operation
+	 * sooner on the physical standby.
+	 */
+	SlotSyncWorkerRegister();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
@@ -5740,6 +5747,9 @@ bgworker_should_start_now(BgWorkerStartTime start_time)
 		case PM_HOT_STANDBY:
 			if (start_time == BgWorkerStart_ConsistentState)
 				return true;
+			if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
+					 pmState != PM_RUN)
+				return true;
 			/* fall through */
 
 		case PM_RECOVERY:
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index cf11b98da3..f96f377d29 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -418,6 +421,44 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..c21d081346 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,31 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	if (RecoveryInProgress())
+	{
+		/*
+		 * Do not allow consumption of a "synchronized" slot until the standby
+		 * gets promoted.
+		 */
+		if (slot->data.sync_state != SYNCSLOT_STATE_NONE)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot use replication slot \"%s\" for logical "
+						"decoding", NameStr(slot->data.name)),
+					 errdetail("This slot is being synced from the primary server."),
+					 errhint("Specify another replication slot.")));
+	}
+	else
+	{
+		/*
+		 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+		 * promotion.
+		 */
+		if (slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			elog(ERROR, "replication slot \"%s\" was not synced completely "
+				"from the primary server", NameStr(slot->data.name));
+	}
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..175d3c6910
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1319 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * While creating the slot on physical standby, if the local restart_lsn and/or
+ * local catalog_xmin is ahead of those on the remote then the worker cannot
+ * create the local slot in sync with the primary server because that would
+ * mean moving the local slot backwards and the standby might not have WALs
+ * retained for old LSN. In this case, the worker will wait for the primary
+ * server slot's restart_lsn and catalog_xmin to catch up with the local one
+ * before attempting the actual sync. Meanwhile, it will persist the slot with
+ * sync_state as SYNCSLOT_STATE_INITIATED('i'). Once the primary server catches
+ * up, it will move the slot to SYNCSLOT_STATE_READY('r') state and will perform
+ * the sync periodically.
+ *
+ * The worker also takes care of dropping the slots which were created by it
+ * and are currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/table.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_database.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/*
+ * Struct for sharing information between startup process and slot
+ * sync worker.
+ *
+ * Slot sync worker's pid is needed by startup process in order to
+ * shut it down during promotion.
+ */
+typedef struct SlotSyncWorkerCtx
+{
+	pid_t		pid;
+	slock_t		mutex;
+} SlotSyncWorkerCtx;
+
+SlotSyncWorkerCtx *SlotSyncWorker = NULL;
+
+/* GUC variable */
+bool		enable_syncslot = false;
+
+/* The last sync-cycle time when the worker updated any of the slots. */
+static TimestampTz last_update_time;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn);
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WAIT_PRIMARY_CATCHUP_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * If passed, *wait_attempts_exceeded will be set to true only if this
+ * function exits due to exhausting its wait attempts. It will be false
+ * in all the other cases.
+ *
+ * Returns true if remote_slot could catch up with the locally reserved
+ * position.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *wait_attempts_exceeded)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+#define WAIT_PRIMARY_CATCHUP_ATTEMPTS 5
+
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+	Assert(wait_attempts_exceeded == NULL || *wait_attempts_exceeded == false);
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn,"
+					 " confirmed_flush_lsn, catalog_xmin"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		XLogRecPtr	new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *tupslot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Handle any termination request if any */
+		ProcessSlotSyncInterrupts(wrconn);
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot \"%s\" info from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" creation aborted", remote_slot->name),
+					 errdetail("This slot was not found on the primary server")));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(tupslot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(tupslot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" creation aborted", remote_slot->name),
+					 errdetail("This slot was invalidated on the primary server")));
+			pfree(cmd.data);
+			ExecClearTuple(tupslot);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * Having got a valid restart_lsn, the confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(tupslot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(tupslot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(tupslot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WAIT_PRIMARY_CATCHUP_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\"",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			if (wait_attempts_exceeded)
+				*wait_attempts_exceeded = true;
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up (refer
+ * to the comment atop the file for details on this wait)
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			local_slots = lappend(local_slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(local_slots);
+}
+
+/*
+ * Get list of local logical slots which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slots(void)
+{
+	List	   *local_slots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			local_slots = lappend(local_slots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return local_slots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+check_sync_slot_on_remote(ReplicationSlot *local_slot, List *remote_slots,
+						  bool *locally_invalidated)
+{
+	ListCell   *lc;
+
+	foreach(lc, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			SpinLockAcquire(&local_slot->mutex);
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+			SpinLockRelease(&local_slot->mutex);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Additionally, it drops slots that are valid on the primary but got
+ * invalidated on the standby. This situation may occur due to the following
+ * reasons:
+ * - The max_slot_wal_keep_size on the standby is insufficient to retain WAL
+ *   records from the restart_lsn of the slot.
+ * - primary_slot_name is temporarily reset to null and the physical slot is
+ *   removed.
+ * - The primary changes wal_level to a level lower than logical.
+ *
+ * The assumption is that these dropped local invalidated slots will get
+ * recreated in next sync-cycle and it is okay to drop and recreate such slots
+ * as long as these are not consumable on the standby (which is the case
+ * currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	local_slots = get_local_synced_slots();
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc);
+		bool		remote_exists = false;
+		bool		locally_invalidated = false;
+
+		remote_exists = check_sync_slot_on_remote(local_slot, remote_slot_list,
+												  &locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!remote_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ *
+ * Returns TRUE if the local slot is updated.
+ */
+static bool
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot)
+{
+	ReplicationSlot *slot;
+	bool		slot_updated = false;
+
+	/*
+	 * Sanity check: Make sure that concerned WAL is received before syncing
+	 * slot to target lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* Search for the named slot */
+	if ((slot = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		char		sync_state;
+
+		SpinLockAcquire(&slot->mutex);
+		sync_state = slot->data.sync_state;
+		SpinLockRelease(&slot->mutex);
+
+		/* User created slot with the same name exists, raise ERROR. */
+		if (sync_state == SYNCSLOT_STATE_NONE)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("skipping sync of slot \"%s\" as it is a user created"
+							" slot", remote_slot->name),
+					 errdetail("This slot has failover enabled on the primary and"
+							   " thus is sync candidate but user created slot with"
+							   " the same name already exists on the standby")));
+
+		/*
+		 * Slot created by the slot sync worker exists, sync it.
+		 *
+		 * It is important to acquire the slot here before checking
+		 * invalidation. If we don't acquire the slot first, there could be a
+		 * race condition that the local slot could be invalidated just after
+		 * checking the 'invalidated' flag here and we could end up
+		 * overwriting 'invalidated' flag to remote_slot's value. See
+		 * InvalidatePossiblyObsoleteSlot() where it invalidates slot directly
+		 * if the slot is not acquired by other processes.
+		 */
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		Assert(slot == MyReplicationSlot);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (slot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&slot->mutex);
+			slot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Skip the sync of an invalidated slot */
+		if (slot->data.invalidated != RS_INVAL_NONE)
+		{
+			ReplicationSlotRelease();
+			return false;
+		}
+
+		/* Slot ready for sync, so sync it. */
+		if (sync_state == SYNCSLOT_STATE_READY)
+		{
+			/*
+			 * Sanity check: With hot_standby_feedback enabled and
+			 * invalidations handled appropriately as above, this should never
+			 * happen.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn)
+				elog(ERROR,
+					 "not synchronizing local slot \"%s\" LSN(%X/%X)"
+					 " to remote slot's LSN(%X/%X) as synchronization "
+					 " would move it backwards", remote_slot->name,
+					 LSN_FORMAT_ARGS(slot->data.restart_lsn),
+					 LSN_FORMAT_ARGS(remote_slot->restart_lsn));
+
+			if (remote_slot->confirmed_lsn != slot->data.confirmed_flush ||
+				remote_slot->restart_lsn != slot->data.restart_lsn ||
+				remote_slot->catalog_xmin != slot->data.catalog_xmin)
+			{
+				/* Update LSN of slot to remote slot's current position */
+				local_slot_update(remote_slot);
+				ReplicationSlotSave();
+				slot_updated = true;
+			}
+		}
+		/* Slot not ready yet, let's attempt to make it sync-ready now. */
+		else if (sync_state == SYNCSLOT_STATE_INITIATED)
+		{
+			/*
+			 * Wait for the primary server to catch-up. Refer to the comment
+			 * atop the file for details on this wait.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+				TransactionIdPrecedes(remote_slot->catalog_xmin,
+									  slot->data.catalog_xmin))
+			{
+				if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+				{
+					ReplicationSlotRelease();
+					return false;
+				}
+			}
+
+			/*
+			 * Wait for primary is over, update the lsns and mark the slot as
+			 * READY for further syncs.
+			 */
+			local_slot_update(remote_slot);
+			SpinLockAcquire(&slot->mutex);
+			slot->data.sync_state = SYNCSLOT_STATE_READY;
+			SpinLockRelease(&slot->mutex);
+
+			/* Save the changes */
+			ReplicationSlotMarkDirty();
+			ReplicationSlotSave();
+			slot_updated = true;
+
+			ereport(LOG,
+					errmsg("newly locally created slot \"%s\" is sync-ready now",
+						   remote_slot->name));
+		}
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+
+		/* Ensure that we have transaction env needed by get_database_oid() */
+		Assert(IsTransactionState());
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover,
+							  SYNCSLOT_STATE_INITIATED);
+
+		/* For shorter lines. */
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * Wait for the primary server to catch-up. Refer to the comment atop
+		 * the file for details on this wait.
+		 */
+		if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  slot->data.catalog_xmin))
+		{
+			bool		wait_attempts_exceeded = false;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &wait_attempts_exceeded))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid) and attempt the wait and synchronization in
+				 * the next cycle.
+				 */
+				if (wait_attempts_exceeded)
+				{
+					ReplicationSlotPersist();
+					slot_updated = true;
+				}
+
+				ReplicationSlotRelease();
+				return slot_updated;
+			}
+		}
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&slot->mutex);
+		slot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&slot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		slot_updated = true;
+
+		ereport(LOG,
+				errmsg("newly locally created slot \"%s\" is sync-ready now",
+					   remote_slot->name));
+	}
+
+	ReplicationSlotRelease();
+
+	return slot_updated;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and updates
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns TRUE if any of the slots gets updated in this sync-cycle.
+ */
+static bool
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *tupslot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	ListCell   *lc;
+	bool		some_slot_updated = false;
+
+	/*
+	 * The primary_slot_name is not set yet or WALs not received yet.
+	 * Synchronization is not possible if the walreceiver is not started.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		return false;
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	StartTransactionCommand();
+
+	initStringInfo(&s);
+
+	/* Construct query to fetch slots with failover enabled. */
+	appendStringInfo(&s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover");
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(tupslot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(tupslot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(tupslot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(tupslot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(tupslot, 5,
+																	   &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(tupslot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(tupslot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(tupslot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(tupslot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(tupslot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(lc, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		some_slot_updated |= synchronize_one_slot(wrconn, remote_slot);
+	}
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	CommitTransactionCommand();
+
+	return some_slot_updated;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker",
+							&err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Checks the primary server info.
+ *
+ * Using the specified primary server connection, check whether we are cascading
+ * standby. It also validates primary_slot_name for non-cascading-standbys.
+ */
+static void
+check_primary_info(WalReceiverConn *wrconn, bool *am_cascading_standby)
+{
+#define PRIMARY_INFO_OUTPUT_COL_COUNT 2
+	WalRcvExecResult *res;
+	Oid			slotRow[PRIMARY_INFO_OUTPUT_COL_COUNT] = {BOOLOID, BOOLOID};
+	StringInfoData cmd;
+	bool		isnull;
+	TupleTableSlot *tupslot;
+	bool		valid;
+	bool		remote_in_recovery;
+	bool		tuple_ok PG_USED_FOR_ASSERTS_ONLY;
+
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	StartTransactionCommand();
+
+	Assert(am_cascading_standby != NULL);
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT pg_is_in_recovery(), count(*) = 1"
+					 " FROM pg_replication_slots"
+					 " WHERE slot_type='physical' AND slot_name=%s",
+					 quote_literal_cstr(PrimarySlotName));
+
+	res = walrcv_exec(wrconn, cmd.data, PRIMARY_INFO_OUTPUT_COL_COUNT, slotRow);
+	pfree(cmd.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch primary_slot_name \"%s\" info from the "
+						"primary: %s", PrimarySlotName, res->err)));
+
+	tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	tuple_ok = tuplestore_gettupleslot(res->tuplestore, true, false, tupslot);
+	Assert(tuple_ok);			/* It must return one tuple */
+
+	remote_in_recovery = DatumGetBool(slot_getattr(tupslot, 1, &isnull));
+	Assert(!isnull);
+
+	/* No need to check further, return that we are cascading standby */
+	if (remote_in_recovery)
+	{
+		*am_cascading_standby = true;
+		CommitTransactionCommand();
+		return;
+	}
+
+	valid = DatumGetBool(slot_getattr(tupslot, 2, &isnull));
+	Assert(!isnull);
+
+	if (!valid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("exiting from slot synchronization due to bad configuration"),
+		/* translator: second %s is a GUC variable name */
+				 errdetail("The primary slot \"%s\" specified by %s is not valid.",
+						   PrimarySlotName, "primary_slot_name")));
+	ExecClearTuple(tupslot);
+	walrcv_clear_result(res);
+
+	CommitTransactionCommand();
+
+	*am_cascading_standby = false;
+}
+
+/*
+ * Check that all necessary GUCs for slot synchronization are set
+ * appropriately. If not, raise an ERROR.
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+	/* Sanity check. */
+	Assert(enable_syncslot);
+
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_slot_name"));
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be enabled.", "hot_standby_feedback"));
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("wal_level must be >= logical."));
+
+	/*
+	 * The primary_conninfo is required to make connection to primary for
+	 * getting slots information.
+	 */
+	if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_conninfo"));
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	*dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (*dbname == NULL)
+		ereport(ERROR,
+
+		/*
+		 * translator: 'dbname' is a specific option; %s is a GUC variable
+		 * name
+		 */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("'dbname' must be specified in %s.", "primary_conninfo"));
+}
+
+/*
+ * Exit the slot sync worker with given exit-code.
+ */
+static void
+slotsync_worker_exit(const char *msg, int code)
+{
+	ereport(LOG, errmsg("%s", msg));
+	proc_exit(code);
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs have changed, exit the worker and
+ * let it get restarted by the postmaster.
+ */
+static void
+slotsync_reread_config(WalReceiverConn *wrconn)
+{
+	char	   *old_primary_conninfo = pstrdup(PrimaryConnInfo);
+	char	   *old_primary_slotname = pstrdup(PrimarySlotName);
+	bool		old_hot_standby_feedback = hot_standby_feedback;
+	bool		conninfo_changed;
+	bool		primary_slotname_changed;
+	bool		restart = false;
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	conninfo_changed = strcmp(old_primary_conninfo, PrimaryConnInfo) != 0;
+	primary_slotname_changed = strcmp(old_primary_slotname, PrimarySlotName) != 0;
+
+	restart = conninfo_changed || primary_slotname_changed ||
+		(old_hot_standby_feedback != hot_standby_feedback);
+
+	pfree(old_primary_conninfo);
+	pfree(old_primary_slotname);
+
+	if (restart)
+	{
+		char	   *msg = "slot sync worker will restart because of a parameter change";
+
+		/*
+		 * The exit code 1 will make postmaster restart the slot sync worker.
+		 */
+		slotsync_worker_exit(msg, 1 /* proc_exit code */ );
+	}
+}
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		char	   *msg = "replication slot sync worker is shutting down on receiving SIGINT";
+
+		walrcv_disconnect(wrconn);
+
+		/*
+		 * The exit code 0 means slot sync worker will not be restarted by
+		 * postmaster.
+		 */
+		slotsync_worker_exit(msg, 0 /* proc_exit code */ );
+	}
+
+	if (ConfigReloadPending)
+		slotsync_reread_config(wrconn);
+}
+
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = InvalidPid;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+	bool		am_cascading_standby;
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	Assert(SlotSyncWorker->pid == InvalidPid);
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	validate_slotsync_parameters(&dbname);
+
+	/*
+	 * Connect to the database specified by user in primary_conninfo. We need
+	 * a database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/*
+	 * Using the specified primary server connection, check whether we are
+	 * cascading standby and validates primary_slot_name for
+	 * non-cascading-standbys.
+	 */
+	check_primary_info(wrconn, &am_cascading_standby);
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+		TimestampTz now;
+		bool		some_slot_updated;
+
+		ProcessSlotSyncInterrupts(wrconn);
+
+		if (am_cascading_standby)
+		{
+			/*
+			 * Slot synchronization is currently not supported on cascading
+			 * standby. So if we are on the cascading standby, skip the sync
+			 * and take a longer nap before we check again whether we are
+			 * still cascading standby or not.
+			 */
+			naptime = 6 * WORKER_INACTIVITY_NAPTIME_MS; /* 60 sec */
+		}
+		else
+		{
+			some_slot_updated = synchronize_slots(wrconn);
+
+			/*
+			 * If any of the slots get updated in this sync-cycle, use default
+			 * naptime and update 'last_update_time'. But if no activity is
+			 * observed in this sync-cycle, then increase naptime provided
+			 * inactivity time reaches threshold.
+			 */
+			now = GetCurrentTimestamp();
+			if (some_slot_updated)
+				last_update_time = now;
+			else if (TimestampDifferenceExceeds(last_update_time,
+												now, WORKER_INACTIVITY_THRESHOLD_MS))
+				naptime = WORKER_INACTIVITY_NAPTIME_MS;
+		}
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+
+		/* Recheck if it is still a cascading standby */
+		if (am_cascading_standby)
+			check_primary_info(wrconn, &am_cascading_standby);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsLogicalSlotSyncWorker(void)
+{
+	return SlotSyncWorker->pid == MyProcPid;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (SlotSyncWorker->pid == InvalidPid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Wait for it to die. */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long. */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (SlotSyncWorker->pid == InvalidPid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Allocate and initialize slot sync worker shared memory
+ */
+void
+SlotSyncWorkerShmemInit(void)
+{
+	Size		size;
+	bool		found;
+
+	size = sizeof(SlotSyncWorkerCtx);
+	size = MAXALIGN(size);
+
+	SlotSyncWorker = (SlotSyncWorkerCtx *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
+
+	if (!found)
+	{
+		memset(SlotSyncWorker, 0, size);
+		SlotSyncWorker->pid = InvalidPid;
+		SpinLockInit(&SlotSyncWorker->mutex);
+	}
+}
+
+/*
+ * Register the background worker for slots synchronization provided
+ * enable_syncslot is ON.
+ */
+void
+SlotSyncWorkerRegister(void)
+{
+	BackgroundWorker bgw;
+
+	if (!enable_syncslot)
+	{
+		ereport(LOG,
+				(errmsg("skipping slot synchronization"),
+				 errdetail("enable_syncslot is disabled.")));
+		return;
+	}
+
+	memset(&bgw, 0, sizeof(bgw));
+
+	/* We need database connection which needs shared-memory access as well. */
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+
+	/* Start as soon as a consistent state has been reached in a hot standby */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState_HotStandby;
+
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "replication slot sync worker");
+	snprintf(bgw.bgw_type, BGW_MAXLEN,
+			 "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
+	bgw.bgw_notify_pid = 0;
+	bgw.bgw_main_arg = (Datum) 0;
+
+	RegisterBackgroundWorker(&bgw);
+}
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e46a1955e8..7b3784c212 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -141,7 +141,20 @@
  * subscribe to the new primary without losing any data.
  *
  * However, we do not enable failover for slots created by the table sync
- * worker.
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby due to the following reasons:
+ *
+ * - The standby needs to wait for the primary server to catch up because the
+ *   local restart_lsn of the newly created slot on the standby is set using
+ *   the latest redo position (GetXLogReplayRecPtr()), which is typically ahead
+ *   of the primary's restart_lsn.
+ * - The table sync slot's restart_lsn won't be advanced until the state
+ *   becomes SUBREL_STATE_CATCHUP.
+ *
+ * Therefore, if a failover happens before the restart_lsn advances, the table
+ * sync slot will not be synced to the standby. Consequently, we will not be
+ * able to subscribe to the promoted standby due to the absence of the
+ * necessary table sync slot.
  *
  * Additionally, failover is not enabled for the main slot if the table sync is
  * in progress. This is because if a failover occurs while the table sync
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 49d2de5024..fa31d92dc3 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -262,16 +263,22 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     user will only get commit prepared.
  * failover: If enabled, allows the slot to be synced to physical standbys so
  *     that logical replication can be resumed after failover.
+ * sync_state: Defines slot synchronization state. This function is expected
+ *     to receive either SYNCSLOT_STATE_NONE for the user created slots or
+ *     SYNCSLOT_STATE_INITIATED for the slots being synchronized on the physical
+ *     standby.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover)
+					  bool two_phase, bool failover, char sync_state)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
 
 	Assert(MyReplicationSlot == NULL);
+	Assert(sync_state == SYNCSLOT_STATE_NONE ||
+		   sync_state == SYNCSLOT_STATE_INITIATED);
 
 	ReplicationSlotValidateName(name, ERROR);
 
@@ -327,6 +334,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = sync_state;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -686,12 +694,26 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+	{
+		ReplicationSlotRelease();
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary server.")));
+	}
+
 	ReplicationSlotDropAcquired();
 }
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index c87f61666d..e5ba5956aa 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -44,7 +44,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false);
+						  false, SYNCSLOT_STATE_NONE);
 
 	if (immediately_reserve)
 	{
@@ -137,7 +137,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover);
+						  failover, SYNCSLOT_STATE_NONE);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fddc9310c6..8d48d7c8f4 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1071,7 +1071,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false);
+							  false, false, SYNCSLOT_STATE_NONE);
 
 		if (reserve_wal)
 		{
@@ -1102,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover);
+							  two_phase, failover, SYNCSLOT_STATE_NONE);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, true);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0e0ac22bdd..ae2daff87b 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -37,6 +37,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -339,6 +340,7 @@ CreateOrAttachShmemStructs(void)
 	WalRcvShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7298a187d1..d87020821c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3286,6 +3286,17 @@ ProcessInterrupts(void)
 			 */
 			proc_exit(1);
 		}
+		else if (IsLogicalSlotSyncWorker())
+		{
+			elog(DEBUG1,
+				"replication slot sync worker is shutting down due to administrator command");
+
+			/*
+			 * Slot sync worker can be stopped at any time.
+			 * Use exit status 1 so the background worker is restarted.
+			 */
+			proc_exit(1);
+		}
 		else if (IsBackgroundWorker)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 9a54e04821..7d50971fd7 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2020,6 +2021,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_POSTMASTER, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5d940b72cd..39224137e1 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -358,6 +358,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = off			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bbc03bb76b..82a11e4a31 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 214dc6c29e..31aaa731c3 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -17,6 +17,7 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"
 
 extern ObjectAddress CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										bool isTopLevel);
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index e90ff376a6..8559900b70 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -79,6 +79,7 @@ typedef enum
 	BgWorkerStart_PostmasterStart,
 	BgWorkerStart_ConsistentState,
 	BgWorkerStart_RecoveryFinished,
+	BgWorkerStart_ConsistentState_HotStandby,
 } BgWorkerStartTime;
 
 #define BGW_DEFAULT_RESTART_INTERVAL	60
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index bbd71d0b42..945d2608f6 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -22,6 +22,7 @@ extern void TablesyncWorkerMain(Datum main_arg);
 
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
+extern bool IsLogicalSlotSyncWorker(void);
 
 extern void HandleParallelApplyMessageInterrupt(void);
 extern void HandleParallelApplyMessages(void);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 5ddad69348..9e4389b991 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -52,6 +52,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +120,15 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Synchronization state for a logical slot.
+	 *
+	 * The standby can have any value among the possible values of 'i','r' and
+	 * 'n'. For primary, the default is 'n' for all slots but may also be 'r'
+	 * if leftover from a promoted standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)? Only
 	 * relevant for logical slots on the primary server.
@@ -225,9 +242,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover);
+								  bool two_phase, bool failover,
+								  char sync_state);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index f1135762fb..259d0f7065 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -279,6 +279,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -403,6 +418,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -428,6 +444,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 84bb79ac0f..9406a2666f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -237,6 +237,11 @@ extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
+/* Slot sync worker objects */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+extern PGDLLIMPORT bool enable_syncslot;
+
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
@@ -326,6 +331,12 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
+extern void SlotSyncWorkerRegister(void);
+extern void ShutDownSlotSync(void);
+extern void slotsync_drop_initiated_slots(void);
+extern void SlotSyncWorkerShmemInit(void);
+
 #define isParallelApplyWorker(worker) ((worker)->in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
 #define isTablesyncWorker(worker) ((worker)->in_use && \
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
index bd6affaf48..20f9d825e7 100644
--- a/src/test/recovery/t/050_standby_failover_slots_sync.pl
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -296,4 +296,207 @@ is( $primary->safe_psql(
 
 $subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
 
+##################################################
+# Test logical failover slots on the standby
+# Configure standby3 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub3_slot->| ----> subscriber3 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb3_slot--->| ----> standby3 (connected via streaming replication)
+#                                        |                 lsub3_slot(synced_slot)
+##################################################
+
+# Cleanup old standby_slot_names
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = ''
+));
+$primary->start;
+
+$primary->psql('postgres',
+		q{SELECT pg_create_physical_replication_slot('sb3_slot');});
+
+# Create table and publication on primary
+$primary->safe_psql('postgres', "CREATE TABLE tab_mypub3 (a int PRIMARY KEY);");
+$primary->safe_psql('postgres', "CREATE PUBLICATION mypub3 FOR TABLE tab_mypub3;");
+
+$backup_name = 'backup3';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+my $connstr_1 = $primary->connstr;
+$standby3->stop;
+$standby3->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb3_slot'
+});
+$standby3->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+$standby3->start;
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb3_slot'
+));
+$primary->start;
+
+# Restart the standby
+$standby3->restart;
+my $standby3_conninfo = $standby3->connstr . ' dbname=postgres';
+
+# Create a subscriber node
+my $subscriber3 = PostgreSQL::Test::Cluster->new('subscriber3');
+$subscriber3->init(allows_streaming => 'logical');
+$subscriber3->start;
+$subscriber3->safe_psql('postgres', "CREATE TABLE tab_mypub3 (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true & wait for sync to complete.
+$subscriber3->safe_psql('postgres',
+		"CREATE SUBSCRIPTION mysub3 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION mypub3 WITH (slot_name = lsub3_slot, failover = true);");
+$subscriber3->wait_for_subscription_sync;
+
+# Wait for the standby to start sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub3_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub3_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby and is sync ready
+is($standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub3_slot';}),
+	"t|r",
+	'logical slot has failover as true and sync_state as ready on standby');
+
+##################################################
+# Test to confirm that restart_lsn and confirmed_flush_lsn of the logical slot
+# on the primary is synced to the standby
+##################################################
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_mypub3;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_mypub3 SELECT generate_series(1, 10);");
+
+# Get the restart_lsn for the logical slot lsub3_slot on the primary
+my $primary_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';");
+
+# Confirm that restart_lsn of lsub3_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';]);
+is($result, 't', 'restart_lsn of slot lsub3_slot synced to standby');
+
+# Get the confirmed_flush_lsn for the logical slot lsub3_slot on the primary
+$primary_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';");
+
+# Confirm that confirmed_flush_lsn of lsub3_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub3_slot synced to the standby');
+
+##################################################
+# Test that synchronized slot can neither be docoded nor dropped by the user
+##################################################
+
+# Disable hot_standby_feedback
+$standby3->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = off;');
+$standby3->restart;
+
+# Dropping of synced slot should result in error
+my ($result1, $stdout, $stderr) = $standby3->psql('postgres',
+	"SELECT pg_drop_replication_slot('lsub3_slot');");
+ok($stderr =~ /ERROR:  cannot drop replication slot "lsub3_slot"/,
+	"synced slot on standby cannot be dropped");
+
+# Logical decoding on synced slot should result in error
+($result1, $stdout, $stderr) = $standby3->psql('postgres',
+	"select * from pg_logical_slot_get_changes('lsub3_slot',NULL,NULL);");
+ok($stderr =~ /ERROR:  cannot use replication slot "lsub3_slot" for logical decoding/,
+	"logical decoding is not allowed on synced slot");
+
+# Enable hot_standby_feedback and restart standby
+$standby3->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = on;');
+$standby3->restart;
+
+##################################################
+# Create another slot which stays in sync_state as initiated ('i')
+##################################################
+
+# Create a logical slot with failover = true
+$primary->psql('postgres',
+		q{SELECT pg_create_logical_replication_slot('logical_slot','pgoutput', false, true, true);});
+
+# Wait for the standby to start sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"logical_slot\"/,
+	$offset);
+
+# Confirm that the logical slot is created on the standby and is in sync initiated state
+  is($standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'logical_slot';}),
+	"t|i",
+	'logical slot has failover as true and sync_state as initiated on standby');
+
+##################################################
+# Promote the standby3 to primary. Confirm that:
+# a) the sync-ready('r') slot 'lsub3_slot' is retained on new primary
+# b) the initiated('i') slot 'logical_slot'is dropped on promotion
+# c) logical replication for mysub3 is resumed succesfully after failover
+##################################################
+
+$standby3->promote;
+
+# Update subscription with new primary's connection info
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 DISABLE;");
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 CONNECTION '$standby3_conninfo';");
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 ENABLE;");
+
+is($standby3->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots WHERE slot_name in ('logical_slot','lsub3_slot');}),
+	'lsub3_slot',
+	'synced slot retained on new primary');
+
+# Insert data on the new primary
+$standby3->safe_psql('postgres',
+	"INSERT INTO tab_mypub3 SELECT generate_series(11, 20);");
+
+# Confirm that data in tab_mypub3 replicated on subscriber
+is( $subscriber3->safe_psql('postgres', q{SELECT count(*) FROM tab_mypub3;}),
+	"20",
+	'data replicated from new primary');
+
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 199863c6b5..10c6f32a30 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2321,6 +2321,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2579,6 +2580,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorkerCtx
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-18 10:52                                                               ` Amit Kapila <[email protected]>
  2023-12-19 11:47                                                                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Amit Kapila @ 2023-12-18 10:52 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Peter Smith <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Fri, Dec 15, 2023 at 11:03 AM shveta malik <[email protected]> wrote:
>
> Sorry, I missed attaching the patch. PFA v48.
>

Few comments on v48_0002
========================
1.
+static void
+slotsync_reread_config(WalReceiverConn *wrconn)
{
...
+ pfree(old_primary_conninfo);
+ pfree(old_primary_slotname);
+
+ if (restart)
+ {
+ char    *msg = "slot sync worker will restart because of a parameter change";
+
+ /*
+ * The exit code 1 will make postmaster restart the slot sync worker.
+ */
+ slotsync_worker_exit(msg, 1 /* proc_exit code */ );
+ }
...

I don't see the need to explicitly pfree in case we are already
exiting the process because anyway the memory will be released. We can
avoid using the 'restart' variable for this. Also, probably, directly
exiting here makes sense and at another place where this function is
used. I see that in maybe_reread_subscription(), we exit with a 0 code
and still apply worker restarts, so why use a different exit code
here?

2.
+static void
+check_primary_info(WalReceiverConn *wrconn, bool *am_cascading_standby)
{
...
+ remote_in_recovery = DatumGetBool(slot_getattr(tupslot, 1, &isnull));
+ Assert(!isnull);
+
+ /* No need to check further, return that we are cascading standby */
+ if (remote_in_recovery)
+ {
+ *am_cascading_standby = true;
+ CommitTransactionCommand();
+ return;
...
}

Don't we need to clear the result and tuple in case of early return?

3. It would be a good idea to mention about requirements like a
physical slot on primary, hot_standby_feedback, etc. in the commit
message.

4.
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+   bool *wait_attempts_exceeded)
{
...
+ tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+ if (!tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
+ {
+ ereport(WARNING,
+ (errmsg("slot \"%s\" creation aborted", remote_slot->name),
+ errdetail("This slot was not found on the primary server")));
...
+ /*
+ * It is possible to get null values for LSN and Xmin if slot is
+ * invalidated on the primary server, so handle accordingly.
+ */
+ new_invalidated = DatumGetBool(slot_getattr(tupslot, 1, &isnull));
+ Assert(!isnull);
+
+ new_restart_lsn = DatumGetLSN(slot_getattr(tupslot, 2, &isnull));
+ if (new_invalidated || isnull)
+ {
+ ereport(WARNING,
+ (errmsg("slot \"%s\" creation aborted", remote_slot->name),
+ errdetail("This slot was invalidated on the primary server")));
...
}

a. The errdetail message should end with a full stop. Please check all
other errdetail messages in the patch to follow the same guideline.
b. I think saying slot creation aborted is not completely correct
because we would have created the slot especially when it is in 'i'
state. Can we change it to something like: "aborting initial sync for
slot \"%s\""?
c. Also, if the remote_slot is invalidated, ideally, we can even drop
the local slot but it seems that the patch will drop the same before
the next-sync cycle with any other slot that needs to be dropped. If
so, can we add the comment to indicate the same?

5.
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+ Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+ LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+ LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+    remote_slot->catalog_xmin);
+ LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+   remote_slot->restart_lsn);
+
+ SpinLockAcquire(&MyReplicationSlot->mutex);
+ MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+ SpinLockRelease(&MyReplicationSlot->mutex);
...
...

If required, the invalidated flag is updated in the caller as well, so
why do we need to update it here as well?

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-18 10:52                                                               ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-12-19 11:47                                                                 ` shveta malik <[email protected]>
  2023-12-19 13:19                                                                   ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-19 11:47 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; +Cc: Peter Smith <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Mon, Dec 18, 2023 at 4:22 PM Amit Kapila <[email protected]> wrote:
>
> On Fri, Dec 15, 2023 at 11:03 AM shveta malik <[email protected]> wrote:
> >
> > Sorry, I missed attaching the patch. PFA v48.
> >
>
> Few comments on v48_0002
> ========================

Thanks for reviewing. These are addressed in v50. Please find my
comments inline for some of these.

> 1.
> +static void
> +slotsync_reread_config(WalReceiverConn *wrconn)
> {
> ...
> + pfree(old_primary_conninfo);
> + pfree(old_primary_slotname);
> +
> + if (restart)
> + {
> + char    *msg = "slot sync worker will restart because of a parameter change";
> +
> + /*
> + * The exit code 1 will make postmaster restart the slot sync worker.
> + */
> + slotsync_worker_exit(msg, 1 /* proc_exit code */ );
> + }
> ...
>
> I don't see the need to explicitly pfree in case we are already
> exiting the process because anyway the memory will be released. We can
> avoid using the 'restart' variable for this.

I have moved pfree to the end where we do not exit the worker. Removed
restart variable.

>Also, probably, directly
> exiting here makes sense and at another place where this function is
> used. I see that in maybe_reread_subscription(), we exit with a 0 code
> and still apply worker restarts, so why use a different exit code
> here?
>

Logical rep worker is started by logical rep launcher and it has
different logic of restarting it. OTOH, slot-sync worker is started by
the postmaster and the postmaster starts any of its bgworkers only if
the worker had an abnormal exit and restart_time is given during
registration of the worker. Thus we need exit_code here. I have
removed the new function added though.

> 2.
> +static void
> +check_primary_info(WalReceiverConn *wrconn, bool *am_cascading_standby)
> {
> ...
> + remote_in_recovery = DatumGetBool(slot_getattr(tupslot, 1, &isnull));
> + Assert(!isnull);
> +
> + /* No need to check further, return that we are cascading standby */
> + if (remote_in_recovery)
> + {
> + *am_cascading_standby = true;
> + CommitTransactionCommand();
> + return;
> ...
> }
>
> Don't we need to clear the result and tuple in case of early return?

Yes, it was needed. Modified.

>
> 3. It would be a good idea to mention about requirements like a
> physical slot on primary, hot_standby_feedback, etc. in the commit
> message.
>
> 4.
> +static bool
> +wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
> +   bool *wait_attempts_exceeded)
> {
> ...
> + tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
> + if (!tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
> + {
> + ereport(WARNING,
> + (errmsg("slot \"%s\" creation aborted", remote_slot->name),
> + errdetail("This slot was not found on the primary server")));
> ...
> + /*
> + * It is possible to get null values for LSN and Xmin if slot is
> + * invalidated on the primary server, so handle accordingly.
> + */
> + new_invalidated = DatumGetBool(slot_getattr(tupslot, 1, &isnull));
> + Assert(!isnull);
> +
> + new_restart_lsn = DatumGetLSN(slot_getattr(tupslot, 2, &isnull));
> + if (new_invalidated || isnull)
> + {
> + ereport(WARNING,
> + (errmsg("slot \"%s\" creation aborted", remote_slot->name),
> + errdetail("This slot was invalidated on the primary server")));
> ...
> }
>
> a. The errdetail message should end with a full stop. Please check all
> other errdetail messages in the patch to follow the same guideline.
> b. I think saying slot creation aborted is not completely correct
> because we would have created the slot especially when it is in 'i'
> state. Can we change it to something like: "aborting initial sync for
> slot \"%s\""?
> c. Also, if the remote_slot is invalidated, ideally, we can even drop
> the local slot but it seems that the patch will drop the same before
> the next-sync cycle with any other slot that needs to be dropped. If
> so, can we add the comment to indicate the same?

I have added comments. Basically, it will be dropped in caller only if
it is 'RS_EPHEMERAL' state else if it is already persisted, then will
be maintained as is but marked as invalidated in caller and its sync
will be skipped next time onwards.

>
> 5.
> +static void
> +local_slot_update(RemoteSlot *remote_slot)
> +{
> + Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
> +
> + LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
> + LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
> +    remote_slot->catalog_xmin);
> + LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
> +   remote_slot->restart_lsn);
> +
> + SpinLockAcquire(&MyReplicationSlot->mutex);
> + MyReplicationSlot->data.invalidated = remote_slot->invalidated;
> + SpinLockRelease(&MyReplicationSlot->mutex);
> ...
> ...
>
> If required, the invalidated flag is updated in the caller as well, so
> why do we need to update it here as well?
>

It was needed by the part where the slot is not existing and we need
to create a new slot. I have now moved the invalidation check in
caller; we do not create the slot itself if remote_slot is found as
invalidated one in the beginning. And if it is invalidated in between
of the wait logic, then it will be dropped by ReplicationSlotRelease.

> --
> With Regards,
> Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-18 10:52                                                               ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-19 11:47                                                                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-19 13:19                                                                   ` Amit Kapila <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Amit Kapila @ 2023-12-19 13:19 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Peter Smith <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Tue, Dec 19, 2023 at 5:17 PM shveta malik <[email protected]> wrote:
>
> On Mon, Dec 18, 2023 at 4:22 PM Amit Kapila <[email protected]> wrote:
> >
> > On Fri, Dec 15, 2023 at 11:03 AM shveta malik <[email protected]> wrote:
> > >
> > > Sorry, I missed attaching the patch. PFA v48.
> > >
> >
> > Few comments on v48_0002
> > ========================
>
> Thanks for reviewing. These are addressed in v50.
>

I was still reviewing the v48 version and have a few comments as
below. If some of these are already addressed or not relevant, feel
free to ignore them.
1.
+ /*
+ * Slot sync worker can be stopped at any time.
+ * Use exit status 1 so the background worker is restarted.

We don't need to start the second line of comment in a separate line.

2.
+ * The assumption is that these dropped local invalidated slots will get
+ * recreated in next sync-cycle and it is okay to drop and recreate such slots

In the above line '.. local invalidated ..' sounds redundant. Shall we
remove it?

3.
+ if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+ {
+ SpinLockRelease(&WalRcv->mutex);
+ elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "


This error message looks odd to me. At least, it should be exiting
instead of skipping because we won't continue after this.

4.
+ /* User created slot with the same name exists, raise ERROR. */
+ if (sync_state == SYNCSLOT_STATE_NONE)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("skipping sync of slot \"%s\" as it is a user created"
+ " slot", remote_slot->name),
+ errdetail("This slot has failover enabled on the primary and"
+    " thus is sync candidate but user created slot with"
+    " the same name already exists on the standby")));

Same problem as above. The skipping in error message doesn't seem to
be suitable for the purpose. Additionally, errdetail message should
end with a full stop.

5.
+ /* Slot ready for sync, so sync it. */
+ if (sync_state == SYNCSLOT_STATE_READY)
+ {
+ /*
+ * Sanity check: With hot_standby_feedback enabled and
+ * invalidations handled appropriately as above, this should never
+ * happen.
+ */
+ if (remote_slot->restart_lsn < slot->data.restart_lsn)
+ elog(ERROR,
+ "not synchronizing local slot \"%s\" LSN(%X/%X)"
+ " to remote slot's LSN(%X/%X) as synchronization "

The start of the error message sounds odd. Shall we say 'cannot
synchronize ...'?

6. All except one of the callers of local_slot_update() marks the slot
dirty and the same is required as well. I think the remaining caller
should also mark it dirty and we should move
ReplicationSlotMarkDirty() in the caller space.

7.
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot)
{
...
+ /*
+ * Copy the invalidation cause from remote only if local slot is not
+ * invalidated locally, we don't want to overwrite existing one.
+ */
+ if (slot->data.invalidated == RS_INVAL_NONE)
+ {
+ SpinLockAcquire(&slot->mutex);
+ slot->data.invalidated = remote_slot->invalidated;
+ SpinLockRelease(&slot->mutex);
+ }
...

It doesn't seem that after changing the invalidated flag, we always
mark the slot dirty. Am, I missing something?

8.
+ /*
+ * Drop local slots that no longer need to be synced. Do it before
+ * synchronize_one_slot to allow dropping of slots before actual sync
+ * which are invalidated locally while still valid on the primary server.
+ */
+ drop_obsolete_slots(remote_slot_list);

The second part of the above comment seems redundant as that is obvious.

9.
+static WalReceiverConn *
+remote_connect(void)
+{
+ WalReceiverConn *wrconn = NULL;
+ char    *err;
+
+ wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+ cluster_name[0] ? cluster_name : "slotsyncworker",
+ &err);
+ if (wrconn == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONNECTION_FAILURE),
+ errmsg("could not connect to the primary server: %s", err)));
+ return wrconn;
+}

Do we need a function for this? It appears to be called from just one
place, so not sure if it is helpful to have a function for this.

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-18 23:20                                                               ` Peter Smith <[email protected]>
  2023-12-19 06:06                                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Peter Smith @ 2023-12-18 23:20 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Here are some review comments for v48-0002

======
doc/src/sgml/config.sgml

1.
+          If slot synchronization is enabled then it is also necessary to
+          specify <literal>dbname</literal> in the
+          <varname>primary_conninfo</varname> string. This will only
be used for
+          slot synchronization. It is ignored for streaming.

I felt the "If slot synchronization is enabled" part should also
include an xref to the enable_slotsync GUC, otherwise there is no
information here about how to enable it.

SUGGESTION
If slot synchronization is enabled (see XXX) ....

======
doc/src/sgml/logicaldecoding.sgml

2.
+    <para>
+     The ability to resume logical replication after failover depends upon the
+     <link linkend="view-pg-replication-slots">pg_replication_slots</link>.<structfield>sync_state</structfield>
+     value for the synchronized slots on the standby at the time of failover.
+     Only slots that have attained "ready" sync_state ('r') on the standby
+     before failover can be used for logical replication after failover. Slots
+     that have not yet reached 'r' state (they are still 'i') will be dropped,
+     therefore logical replication for those slots cannot be resumed. For
+     example, if the synchronized slot could not become sync-ready on standby
+     due to a disabled subscription, then the subscription cannot be resumed
+     after failover even when it is enabled.
+     If the primary is idle, then the synchronized slots on the standby may
+     take a noticeable time to reach the ready ('r') sync_state. This can
+     be sped up by calling the
+     <function>pg_log_standby_snapshot</function> function on the primary.
+    </para>

2a.
/sync-ready on standby/sync-ready on the standby/

~

2b.
Should "If the primary is idle" be in a new paragraph?

======
doc/src/sgml/system-views.sgml

3.
+      <para>
+      The hot standby can have any of these sync_state values for the slots but
+      on a hot standby, the slots with state 'r' and 'i' can neither be used
+      for logical decoding nor dropped by the user.
+      The sync_state has no meaning on the primary server; the primary
+      sync_state value is default 'n' for all slots but may (if leftover
+      from a promoted standby)  also be 'r'.
+      </para></entry>

I still feel we are exposing too much useless information about the
primary server values.

Isn't it sufficient to just say "The sync_state values have no meaning
on a primary server.", and not bother to mention what those
meaningless values might be -- e.g. if they are meaningless then who
cares what they are or how they got there?

======
src/backend/replication/logical/slotsync.c

4. synchronize_one_slot

+ /* Slot ready for sync, so sync it. */
+ if (sync_state == SYNCSLOT_STATE_READY)
+ {
+ /*
+ * Sanity check: With hot_standby_feedback enabled and
+ * invalidations handled appropriately as above, this should never
+ * happen.
+ */
+ if (remote_slot->restart_lsn < slot->data.restart_lsn)
+ elog(ERROR,
+ "not synchronizing local slot \"%s\" LSN(%X/%X)"
+ " to remote slot's LSN(%X/%X) as synchronization "
+ " would move it backwards", remote_slot->name,
+ LSN_FORMAT_ARGS(slot->data.restart_lsn),
+ LSN_FORMAT_ARGS(remote_slot->restart_lsn));
+
+ if (remote_slot->confirmed_lsn != slot->data.confirmed_flush ||
+ remote_slot->restart_lsn != slot->data.restart_lsn ||
+ remote_slot->catalog_xmin != slot->data.catalog_xmin)
+ {
+ /* Update LSN of slot to remote slot's current position */
+ local_slot_update(remote_slot);
+ ReplicationSlotSave();
+ slot_updated = true;
+ }
+ }
+ /* Slot not ready yet, let's attempt to make it sync-ready now. */
+ else if (sync_state == SYNCSLOT_STATE_INITIATED)
+ {
+ /*
+ * Wait for the primary server to catch-up. Refer to the comment
+ * atop the file for details on this wait.
+ */
+ if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+ TransactionIdPrecedes(remote_slot->catalog_xmin,
+   slot->data.catalog_xmin))
+ {
+ if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+ {
+ ReplicationSlotRelease();
+ return false;
+ }
+ }
+
+ /*
+ * Wait for primary is over, update the lsns and mark the slot as
+ * READY for further syncs.
+ */
+ local_slot_update(remote_slot);
+ SpinLockAcquire(&slot->mutex);
+ slot->data.sync_state = SYNCSLOT_STATE_READY;
+ SpinLockRelease(&slot->mutex);
+
+ /* Save the changes */
+ ReplicationSlotMarkDirty();
+ ReplicationSlotSave();
+ slot_updated = true;
+
+ ereport(LOG,
+ errmsg("newly locally created slot \"%s\" is sync-ready now",
+    remote_slot->name));
+ }

4a.
It would be more natural in the code if you do the
SYNCSLOT_STATE_INITIATED logic before the SYNCSLOT_STATE_READY because
that is the order those states come in.

~

4b.
I'm not sure if it is worth it, but I was thinking that some duplicate
code can be avoided by doing if/if instead of if/else

if (sync_state == SYNCSLOT_STATE_INITIATED)
{
..
}
if (sync_state == SYNCSLOT_STATE_READY)
{
}

By arranging it this way maybe the SYNCSLOT_STATE_INITIATED code block
doesn't need to do anything except update the sync_state =
SYNCSLOT_STATE_READY; Then it can just fall through to the
SYNCSLOT_STATE_READY logic to do all the
local_slot_update(remote_slot); etc in just one place.

~~~

5. check_primary_info

+ * Checks the primary server info.
+ *
+ * Using the specified primary server connection, check whether we
are cascading
+ * standby. It also validates primary_slot_name for non-cascading-standbys.
+ */
+static void
+check_primary_info(WalReceiverConn *wrconn, bool *am_cascading_standby)

5a.
/we are cascading/we are a cascading/

5b.
/non-cascading-standbys./non-cascading standbys./

~~~

6.
+ CommitTransactionCommand();
+
+ *am_cascading_standby = false;

Maybe it's simpler just to set this default false up-front, replacing
the current assert.

BEFORE:
+ Assert(am_cascading_standby != NULL);

AFTER:
*am_cascading_standby = false; /* maybe overwrite later */

~~~

7.
+/*
+ * Exit the slot sync worker with given exit-code.
+ */
+static void
+slotsync_worker_exit(const char *msg, int code)
+{
+ ereport(LOG, errmsg("%s", msg));
+ proc_exit(code);
+}

This could be written differently (don't pass the exit code, instead
pass a bool) like:

static void
slotsync_worker_exit(const char *msg, bool restart_worker)

By doing it this way, you can keep the special exit code values (0,1)
within this function where you can comment all about them instead of
having scattered comments about exit codes in the callers.

SUGGESTION
ereport(LOG, errmsg("%s", msg));
/* <some big comment here about how the code causes the worker to
restart or not> */
proc_exit(restart_worker ? 1 : 0);

~~~

8. slotsync_reread_config

+ if (restart)
+ {
+ char    *msg = "slot sync worker will restart because of a parameter change";
+
+ /*
+ * The exit code 1 will make postmaster restart the slot sync worker.
+ */
+ slotsync_worker_exit(msg, 1 /* proc_exit code */ );
+ }

Shouldn't that message be written as _(), so that it will get translated?

SUGGESTION
slotsync_worker_exit(_("slot sync worker will restart because of a
parameter change"), true /* restart worker */ );

~~~

9. ProcessSlotSyncInterrupts

+ CHECK_FOR_INTERRUPTS();
+
+ if (ShutdownRequestPending)
+ {
+ char    *msg = "replication slot sync worker is shutting down on
receiving SIGINT";
+
+ walrcv_disconnect(wrconn);
+
+ /*
+ * The exit code 0 means slot sync worker will not be restarted by
+ * postmaster.
+ */
+ slotsync_worker_exit(msg, 0 /* proc_exit code */ );
+ }

Shouldn't that message be written as _(), so that it will be translated?

SUGGESTION
slotsync_worker_exit(_("replication slot sync worker is shutting down
on receiving SIGINT"), false /* don't restart worker */ );

~~~

10.
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+ SpinLockAcquire(&SlotSyncWorker->mutex);
+ SlotSyncWorker->pid = InvalidPid;
+ SpinLockRelease(&SlotSyncWorker->mutex);
+}

IMO it would make sense for this function to be defined adjacent to
the slotsync_worker_exit() function.

~~~

11. ReplSlotSyncWorkerMain

+ /*
+ * Using the specified primary server connection, check whether we are
+ * cascading standby and validates primary_slot_name for
+ * non-cascading-standbys.
+ */
+ check_primary_info(wrconn, &am_cascading_standby);
...
+ /* Recheck if it is still a cascading standby */
+ if (am_cascading_standby)
+ check_primary_info(wrconn, &am_cascading_standby);

Those 2 above calls could be combined if you want. By defaulting the
am_cascading_standby = true when declared, then you could put this
code at the top of the loop instead of having the same code in 2
places:

+ if (am_cascading_standby)
+ check_primary_info(wrconn, &am_cascading_standby);

======
src/include/commands/subscriptioncmds.h

12.
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"

There is #include, but no other code change. Is this needed?

======
src/include/replication/slot.h

13.
+ /*
+ * Synchronization state for a logical slot.
+ *
+ * The standby can have any value among the possible values of 'i','r' and
+ * 'n'. For primary, the default is 'n' for all slots but may also be 'r'
+ * if leftover from a promoted standby.
+ */
+ char sync_state;
+

All that is OK now, but I keep circling back to my original thought
that since this state has no meaning for the primary server then

a) why do we even care what potential values it might have there, and
b) isn't it better to call this field 'standby_sync_state' to
emphasize it only has meaning for the standby?

e.g.
SUGGESTION
/*
 * Synchronization state for a logical slot.
 *
 * The standby can have any value among the possible values of 'i','r' and
 * 'n'. For the primary, this field value has no meaning.
 */
char standby_sync_state;

======
Kind Regards,
Peter Smith.
Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-18 23:20                                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
@ 2023-12-19 06:06                                                                 ` Amit Kapila <[email protected]>
  2023-12-19 11:40                                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Amit Kapila @ 2023-12-19 06:06 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; +Cc: shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Tue, Dec 19, 2023 at 4:51 AM Peter Smith <[email protected]> wrote:
>
>
> ======
> doc/src/sgml/system-views.sgml
>
> 3.
> +      <para>
> +      The hot standby can have any of these sync_state values for the slots but
> +      on a hot standby, the slots with state 'r' and 'i' can neither be used
> +      for logical decoding nor dropped by the user.
> +      The sync_state has no meaning on the primary server; the primary
> +      sync_state value is default 'n' for all slots but may (if leftover
> +      from a promoted standby)  also be 'r'.
> +      </para></entry>
>
> I still feel we are exposing too much useless information about the
> primary server values.
>
> Isn't it sufficient to just say "The sync_state values have no meaning
> on a primary server.", and not bother to mention what those
> meaningless values might be -- e.g. if they are meaningless then who
> cares what they are or how they got there?
>

I feel it would be good to mention somewhere that primary can have
slots in 'r' state, if not here, some other place.

>
> 7.
> +/*
> + * Exit the slot sync worker with given exit-code.
> + */
> +static void
> +slotsync_worker_exit(const char *msg, int code)
> +{
> + ereport(LOG, errmsg("%s", msg));
> + proc_exit(code);
> +}
>
> This could be written differently (don't pass the exit code, instead
> pass a bool) like:
>
> static void
> slotsync_worker_exit(const char *msg, bool restart_worker)
>
> By doing it this way, you can keep the special exit code values (0,1)
> within this function where you can comment all about them instead of
> having scattered comments about exit codes in the callers.
>
> SUGGESTION
> ereport(LOG, errmsg("%s", msg));
> /* <some big comment here about how the code causes the worker to
> restart or not> */
> proc_exit(restart_worker ? 1 : 0);
>

Hmm, I don't see the need for this function in the first place. We can
use proc_exit in the two callers directly.

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-18 23:20                                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-19 06:06                                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-12-19 11:40                                                                   ` shveta malik <[email protected]>
  2023-12-19 13:05                                                                     ` RE: Synchronizing slots from primary to standby Hayato Kuroda (Fujitsu) <[email protected]>
  2023-12-20 06:31                                                                     ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  0 siblings, 2 replies; 113+ messages in thread

From: shveta malik @ 2023-12-19 11:40 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; +Cc: Peter Smith <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Tue, Dec 19, 2023 at 11:37 AM Amit Kapila <[email protected]> wrote:
>
> On Tue, Dec 19, 2023 at 4:51 AM Peter Smith <[email protected]> wrote:
> >
> >
> > ======
> > doc/src/sgml/system-views.sgml
> >
> > 3.
> > +      <para>
> > +      The hot standby can have any of these sync_state values for the slots but
> > +      on a hot standby, the slots with state 'r' and 'i' can neither be used
> > +      for logical decoding nor dropped by the user.
> > +      The sync_state has no meaning on the primary server; the primary
> > +      sync_state value is default 'n' for all slots but may (if leftover
> > +      from a promoted standby)  also be 'r'.
> > +      </para></entry>
> >
> > I still feel we are exposing too much useless information about the
> > primary server values.
> >
> > Isn't it sufficient to just say "The sync_state values have no meaning
> > on a primary server.", and not bother to mention what those
> > meaningless values might be -- e.g. if they are meaningless then who
> > cares what they are or how they got there?
> >
>
> I feel it would be good to mention somewhere that primary can have
> slots in 'r' state, if not here, some other place.
>
> >
> > 7.
> > +/*
> > + * Exit the slot sync worker with given exit-code.
> > + */
> > +static void
> > +slotsync_worker_exit(const char *msg, int code)
> > +{
> > + ereport(LOG, errmsg("%s", msg));
> > + proc_exit(code);
> > +}
> >
> > This could be written differently (don't pass the exit code, instead
> > pass a bool) like:
> >
> > static void
> > slotsync_worker_exit(const char *msg, bool restart_worker)
> >
> > By doing it this way, you can keep the special exit code values (0,1)
> > within this function where you can comment all about them instead of
> > having scattered comments about exit codes in the callers.
> >
> > SUGGESTION
> > ereport(LOG, errmsg("%s", msg));
> > /* <some big comment here about how the code causes the worker to
> > restart or not> */
> > proc_exit(restart_worker ? 1 : 0);
> >
>
> Hmm, I don't see the need for this function in the first place. We can
> use proc_exit in the two callers directly.
>
> --
> With Regards,
> Amit Kapila.


PFA v50 patch-set which addresses comments for  v48-0002 and v49-0002
given in [1], [2] and [3].

TODO:
--Fix CFBot failure.
--Work on correctness of test to merge patch003 to patch002

[1]: https://www.postgresql.org/message-id/CAA4eK1Ko-EBBDkea2R8V8PeveGg10PBswCF7JQdnRu%2BMJP%2BYBQ%40mail...
[2]: https://www.postgresql.org/message-id/CAHut%2BPsyZQZ1A4XcKw-D%3DvcTg16pN9Dw0PzE8W_X7Yz_bv00rQ%40mail...
[3]: https://www.postgresql.org/message-id/CAHut%2BPv86wBZiyOLHxycd8Yj9%3Dk5kzVa1x7Gbp%2B%3Dc1VGT9TG2w%40...


thanks
Shveta


Attachments:

  [application/octet-stream] v50-0003-Additional-test-to-validate-the-restart_lsn-of-s.patch (3.0K, ../../CAJpy0uB88wSeBiBb2eGsFusBmLVj7o_bJidViecf35wrktQc=A@mail.gmail.com/2-v50-0003-Additional-test-to-validate-the-restart_lsn-of-s.patch)
  download | inline diff:
From da36d6161e79b1fcdbf5d69e745143e5a7b9f9ac Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Tue, 19 Dec 2023 16:31:49 +0530
Subject: [PATCH v50 3/3] Additional test to validate the restart_lsn of synced
 slot

---
 .../t/050_standby_failover_slots_sync.pl      | 36 +++++++++++++++++--
 1 file changed, 33 insertions(+), 3 deletions(-)

diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
index 277fc2e079..4b65befc65 100644
--- a/src/test/recovery/t/050_standby_failover_slots_sync.pl
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -357,6 +357,37 @@ is($standby1->safe_psql('postgres',
 	"t|r",
 	'logical slot has failover as true and sync_state as ready on standby');
 
+##################################################
+# Test to confirm that restart_lsn and confirmed_flush_lsn of the logical slot
+# on the primary is synced to the standby
+##################################################
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_int;");
+
+# Get the restart_lsn for the logical slot lsub1_slot on the primary
+my $primary_restart_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Get the confirmed_flush_lsn for the logical slot lsub1_slot on the primary
+my $primary_flush_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, 10);");
+
+# Confirm that restart_lsn of lsub1_slot slot is synced to the standby
+$result = $standby1->safe_psql('postgres',
+	qq[SELECT '$primary_restart_lsn' <= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'restart_lsn of slot lsub1_slot synced to standby');
+
+# Confirm that confirmed_flush_lsn of lsub1_slot slot is synced to the standby
+$result = $standby1->safe_psql('postgres',
+	qq[SELECT '$primary_flush_lsn' <= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub1_slot synced to the standby');
+
 ##################################################
 # Test that a synchronized slot can not be decoded, altered and dropped by the user
 ##################################################
@@ -432,14 +463,13 @@ is($standby1->safe_psql('postgres',
 	'synced slot retained on the new primary');
 
 # Insert data on the new primary
-$primary_row_count = 10;
 $standby1->safe_psql('postgres',
-	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+	"INSERT INTO tab_int SELECT generate_series(11, 20);");
 $standby1->wait_for_catchup('regress_mysub1');
 
 # Confirm that data in tab_int replicated on subscriber
 is( $subscriber1->safe_psql('postgres', q{SELECT count(*) FROM tab_int;}),
-	"$primary_row_count",
+	"20",
 	'data replicated from the new primary');
 
 done_testing();
-- 
2.34.1



  [application/octet-stream] v50-0002-Add-logical-slot-sync-capability-to-the-physical.patch (89.1K, ../../CAJpy0uB88wSeBiBb2eGsFusBmLVj7o_bJidViecf35wrktQc=A@mail.gmail.com/3-v50-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From dbdc8f15142d78c355a60c165c7c7bc029cf5d19 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Tue, 12 Dec 2023 16:55:06 +0800
Subject: [PATCH v50 2/3] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The logical replication slots on the primary can be synchronized to the hot
standby by enabling the failover option during slot creation and setting
'enable_syncslot' on the standby. For the synchronization to work, it is
mandatory to have a physical replication slot between the primary and the
standby, and hot_standby_feedback must be enabled on the standby.

All the failover logical replication slots on the primary (assuming
configurations are appropriate) are automatically created on the physical
standbys and are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

The nap time of the worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. If
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated.

If a logical slot on the primary is valid but is invalidated on the standby,
then that slot is dropped and recreated on the standby in next sync-cycle. It
is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently). This situation may occur due to the
following reasons:
- The max_slot_wal_keep_size on the standby is insufficient to retain WAL
  records from the restart_lsn of the slot.
- primary_slot_name is temporarily reset to null and the physical slot is
  removed.
- The primary changes wal_level to a level lower than logical.

The slots synchronization status on the standby can be monitored using
'sync_state' column of pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but slot is not ready yet for periodic syncs,
'r': ready for periodic syncs.
---
 doc/src/sgml/bgworker.sgml                    |   65 +-
 doc/src/sgml/config.sgml                      |   33 +-
 doc/src/sgml/logicaldecoding.sgml             |   31 +
 doc/src/sgml/system-views.sgml                |   35 +
 src/backend/access/transam/xlogrecovery.c     |   18 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    4 +
 src/backend/postmaster/postmaster.c           |   10 +
 .../libpqwalreceiver/libpqwalreceiver.c       |   41 +
 src/backend/replication/logical/Makefile      |    1 +
 src/backend/replication/logical/logical.c     |   25 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1315 +++++++++++++++++
 src/backend/replication/logical/worker.c      |   15 +-
 src/backend/replication/slot.c                |   34 +-
 src/backend/replication/slotfuncs.c           |   37 +-
 src/backend/replication/walsender.c           |    6 +-
 src/backend/storage/ipc/ipci.c                |    2 +
 src/backend/tcop/postgres.c                   |   11 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   10 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/postmaster/bgworker.h             |    1 +
 src/include/replication/logicalworker.h       |    1 +
 src/include/replication/slot.h                |   22 +-
 src/include/replication/walreceiver.h         |   18 +
 src/include/replication/worker_internal.h     |   11 +
 .../t/050_standby_failover_slots_sync.pl      |  145 ++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    2 +
 32 files changed, 1885 insertions(+), 33 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2c393385a9..a7cfe6c58c 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -114,18 +114,59 @@ typedef struct BackgroundWorker
 
   <para>
    <structfield>bgw_start_time</structfield> is the server state during which
-   <command>postgres</command> should start the process; it can be one of
-   <literal>BgWorkerStart_PostmasterStart</literal> (start as soon as
-   <command>postgres</command> itself has finished its own initialization; processes
-   requesting this are not eligible for database connections),
-   <literal>BgWorkerStart_ConsistentState</literal> (start as soon as a consistent state
-   has been reached in a hot standby, allowing processes to connect to
-   databases and run read-only queries), and
-   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system has
-   entered normal read-write state).  Note the last two values are equivalent
-   in a server that's not a hot standby.  Note that this setting only indicates
-   when the processes are to be started; they do not stop when a different state
-   is reached.
+   <command>postgres</command> should start the process. Note that this setting
+   only indicates when the processes are to be started; they do not stop when
+   a different state is reached. Possible values are:
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>BgWorkerStart_PostmasterStart</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_PostmasterStart</primary></indexterm>
+       Start as soon as postgres itself has finished its own initialization;
+       processes requesting this are not eligible for database connections.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState</primary></indexterm>
+       Start as soon as a consistent state has been reached in a hot-standby,
+       allowing processes to connect to databases and run read-only queries.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState_HotStandby</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState_HotStandby</primary></indexterm>
+       Same meaning as <literal>BgWorkerStart_ConsistentState</literal> but
+       it is more strict in terms of the server i.e. start the worker only
+       if it is hot-standby.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_RecoveryFinished</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_RecoveryFinished</primary></indexterm>
+       Start as soon as the system has entered normal read-write state. Note
+       that the <literal>BgWorkerStart_ConsistentState</literal> and
+      <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
+       in a server that's not a hot standby.
+      </para>
+     </listitem>
+    </varlistentry>
+
+   </variablelist>
   </para>
 
   <para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 7993fe3cdd..bbb755f057 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4373,6 +4373,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must configure
+        <literal>enable_syncslot = true</literal> so they can receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4568,8 +4574,13 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           <varname>primary_conninfo</varname> string, or in a separate
           <filename>~/.pgpass</filename> file on the standby server (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          If slot synchronization is enabled (see
+          <xref linkend="guc-enable-syncslot"/>) then it is also
+          necessary to specify <literal>dbname</literal> in the
+          <varname>primary_conninfo</varname> string. This will only be used for
+          slot synchronization. It is ignored for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4894,6 +4905,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..de6cdbe2bc 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,37 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and setting
+     <xref linkend="guc-enable-syncslot"/> on the standby. For the synchronization
+     to work, it is mandatory to have a physical replication slot between the
+     primary and the standby, and <varname>hot_standby_feedback</varname> must
+     be enabled on the standby. It's also highly recommended that the said
+     physical replication slot is named in <varname>standby_slot_names</varname>
+     list on the primary, to prevent the subscriber from consuming changes
+     faster than the hot standby.
+    </para>
+
+    <para>
+     The ability to resume logical replication after failover depends upon the
+     <link linkend="view-pg-replication-slots">pg_replication_slots</link>.<structfield>sync_state</structfield>
+     value for the synchronized slots on the standby at the time of failover.
+     Only slots that have attained "ready" sync_state ('r') on the standby
+     before failover can be used for logical replication after failover. Slots
+     that have not yet reached 'r' state (they are still 'i') will be dropped,
+     therefore logical replication for those slots cannot be resumed. For
+     example, if the synchronized slot could not become sync-ready on the
+     standby due to a disabled subscription, then the subscription cannot be
+     resumed after failover even when it is enabled.
+    </para>
+    <para>
+     If the primary is idle, then the synchronized slots on the standby may
+     take a noticeable time to reach the ready ('r') sync_state. This can
+     be sped up by calling the
+     <function>pg_log_standby_snapshot</function> function on the primary.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..9847342601 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,41 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has configured <xref linkend="guc-enable-syncslot"/> = true.
+      Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para><literal>n</literal> = none for user created slots,
+         </para>
+        </listitem>
+        <listitem>
+         <para><literal>i</literal> = sync initiated for the slot but slot
+         is not ready yet for periodic syncs,
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>r</literal> = ready for periodic syncs.
+         </para>
+        </listitem>
+       </itemizedlist>
+      </para>
+      <para>
+      The hot standby can have any of these sync_state values for the slots but
+      on a hot standby, the slots with state 'r' and 'i' can neither be used
+      for logical decoding nor dropped by the user.
+      The sync_state has no meaning on the primary server; the primary
+      sync_state value is default 'n' for all slots but may (if leftover
+      from a promoted standby)  also be 'r'.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index a2c8fa3981..444f4d23cc 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1436,23 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 *
+	 * We do not update the sync_state from READY to NONE here, as any failed
+	 * update could leave some slots in the 'NONE' state, causing issues during
+	 * slot sync after restarting the server as a standby. While updating after
+	 * switching to the new timeline is an option, it does not simplify the
+	 * handling for both READY and NONE state slots. Therefore, we retain the
+	 * READY state slots after promotion as they can provide useful information
+	 * about their origin.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 3c99cf6047..7f74f53ad1 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -21,6 +21,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "replication/worker_internal.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -129,6 +130,9 @@ static const struct
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 651b85ea74..9b74a49663 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -115,6 +115,7 @@
 #include "postmaster/syslogger.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -1003,6 +1004,12 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	/*
+	 * Register the slot sync worker here to kick start slot-sync operation
+	 * sooner on the physical standby.
+	 */
+	SlotSyncWorkerRegister();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
@@ -5740,6 +5747,9 @@ bgworker_should_start_now(BgWorkerStartTime start_time)
 		case PM_HOT_STANDBY:
 			if (start_time == BgWorkerStart_ConsistentState)
 				return true;
+			if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
+					 pmState != PM_RUN)
+				return true;
 			/* fall through */
 
 		case PM_RECOVERY:
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index cf11b98da3..f96f377d29 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -418,6 +421,44 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..c21d081346 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,31 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	if (RecoveryInProgress())
+	{
+		/*
+		 * Do not allow consumption of a "synchronized" slot until the standby
+		 * gets promoted.
+		 */
+		if (slot->data.sync_state != SYNCSLOT_STATE_NONE)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot use replication slot \"%s\" for logical "
+						"decoding", NameStr(slot->data.name)),
+					 errdetail("This slot is being synced from the primary server."),
+					 errhint("Specify another replication slot.")));
+	}
+	else
+	{
+		/*
+		 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+		 * promotion.
+		 */
+		if (slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			elog(ERROR, "replication slot \"%s\" was not synced completely "
+				"from the primary server", NameStr(slot->data.name));
+	}
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..110ee2a746
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1315 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * While creating the slot on physical standby, if the local restart_lsn and/or
+ * local catalog_xmin is ahead of those on the remote then the worker cannot
+ * create the local slot in sync with the primary server because that would
+ * mean moving the local slot backwards and the standby might not have WALs
+ * retained for old LSN. In this case, the worker will wait for the primary
+ * server slot's restart_lsn and catalog_xmin to catch up with the local one
+ * before attempting the actual sync. Meanwhile, it will persist the slot with
+ * sync_state as SYNCSLOT_STATE_INITIATED('i'). Once the primary server catches
+ * up, it will move the slot to SYNCSLOT_STATE_READY('r') state and will perform
+ * the sync periodically.
+ *
+ * The worker also takes care of dropping the slots which were created by it
+ * and are currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/table.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_database.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/*
+ * Struct for sharing information between startup process and slot
+ * sync worker.
+ *
+ * Slot sync worker's pid is needed by startup process in order to
+ * shut it down during promotion.
+ */
+typedef struct SlotSyncWorkerCtx
+{
+	pid_t		pid;
+	slock_t		mutex;
+} SlotSyncWorkerCtx;
+
+SlotSyncWorkerCtx *SlotSyncWorker = NULL;
+
+/* GUC variable */
+bool		enable_syncslot = false;
+
+/* The last sync-cycle time when the worker updated any of the slots. */
+static TimestampTz last_update_time;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn);
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WAIT_PRIMARY_CATCHUP_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * If passed, *wait_attempts_exceeded will be set to true only if this
+ * function exits due to exhausting its wait attempts. It will be false
+ * in all the other cases.
+ *
+ * Returns true if remote_slot could catch up with the locally reserved
+ * position.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *wait_attempts_exceeded)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+#define WAIT_PRIMARY_CATCHUP_ATTEMPTS 5
+
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+	Assert(wait_attempts_exceeded == NULL || *wait_attempts_exceeded == false);
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn,"
+					 " confirmed_flush_lsn, catalog_xmin"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		bool		new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *tupslot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Handle any termination request if any */
+		ProcessSlotSyncInterrupts(wrconn);
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot \"%s\" info from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
+		{
+			ereport(WARNING,
+					(errmsg("aborting initial sync for slot \"%s\"",
+							remote_slot->name),
+					 errdetail("This slot was not found on the primary server.")));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(tupslot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(tupslot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			/*
+			 * If the local-slot is in 'RS_EPHEMERAL' state, it will not be
+			 * persisted in the caller and ReplicationSlotRelease() will drop
+			 * it. But if the local slot is already persisted and has 'i'
+			 * sync_state, then it will be marked as invalidated in the caller
+			 * and next time onwards its sync will be skipped.
+			 */
+			ereport(WARNING,
+					(errmsg("aborting initial sync for slot \"%s\"",
+							remote_slot->name),
+					 errdetail("This slot was invalidated on the primary server.")));
+			pfree(cmd.data);
+			ExecClearTuple(tupslot);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * Having got a valid restart_lsn, the confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(tupslot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(tupslot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(tupslot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WAIT_PRIMARY_CATCHUP_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\"",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			if (wait_attempts_exceeded)
+				*wait_attempts_exceeded = true;
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up (refer
+ * to the comment atop the file for details on this wait)
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			local_slots = lappend(local_slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(local_slots);
+}
+
+/*
+ * Get list of local logical slots which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slots(void)
+{
+	List	   *local_slots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			local_slots = lappend(local_slots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return local_slots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+check_sync_slot_on_remote(ReplicationSlot *local_slot, List *remote_slots,
+						  bool *locally_invalidated)
+{
+	ListCell   *lc;
+
+	foreach(lc, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			SpinLockAcquire(&local_slot->mutex);
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+			SpinLockRelease(&local_slot->mutex);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Additionally, it drops slots that are valid on the primary but got
+ * invalidated on the standby. This situation may occur due to the following
+ * reasons:
+ * - The max_slot_wal_keep_size on the standby is insufficient to retain WAL
+ *   records from the restart_lsn of the slot.
+ * - primary_slot_name is temporarily reset to null and the physical slot is
+ *   removed.
+ * - The primary changes wal_level to a level lower than logical.
+ *
+ * The assumption is that these dropped local invalidated slots will get
+ * recreated in next sync-cycle and it is okay to drop and recreate such slots
+ * as long as these are not consumable on the standby (which is the case
+ * currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	local_slots = get_local_synced_slots();
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc);
+		bool		remote_exists = false;
+		bool		locally_invalidated = false;
+
+		remote_exists = check_sync_slot_on_remote(local_slot, remote_slot_list,
+												  &locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!remote_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ *
+ * Returns TRUE if the local slot is updated.
+ */
+static bool
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot)
+{
+	ReplicationSlot *slot;
+	bool		slot_updated = false;
+
+	/*
+	 * Sanity check: Make sure that concerned WAL is received before syncing
+	 * slot to target lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* Search for the named slot */
+	if ((slot = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		char		sync_state;
+
+		SpinLockAcquire(&slot->mutex);
+		sync_state = slot->data.sync_state;
+		SpinLockRelease(&slot->mutex);
+
+		/* User created slot with the same name exists, raise ERROR. */
+		if (sync_state == SYNCSLOT_STATE_NONE)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("skipping sync of slot \"%s\" as it is a user created"
+							" slot", remote_slot->name),
+					 errdetail("This slot has failover enabled on the primary and"
+							   " thus is sync candidate but user created slot with"
+							   " the same name already exists on the standby.")));
+
+		/*
+		 * Slot created by the slot sync worker exists, sync it.
+		 *
+		 * It is important to acquire the slot here before checking
+		 * invalidation. If we don't acquire the slot first, there could be a
+		 * race condition that the local slot could be invalidated just after
+		 * checking the 'invalidated' flag here and we could end up
+		 * overwriting 'invalidated' flag to remote_slot's value. See
+		 * InvalidatePossiblyObsoleteSlot() where it invalidates slot directly
+		 * if the slot is not acquired by other processes.
+		 */
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		Assert(slot == MyReplicationSlot);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (slot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&slot->mutex);
+			slot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Skip the sync of an invalidated slot */
+		if (slot->data.invalidated != RS_INVAL_NONE)
+		{
+			ReplicationSlotRelease();
+			return false;
+		}
+
+		/* Slot not ready yet, let's attempt to make it sync-ready now. */
+		if (sync_state == SYNCSLOT_STATE_INITIATED)
+		{
+			/*
+			 * Wait for the primary server to catch-up. Refer to the comment
+			 * atop the file for details on this wait.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+				TransactionIdPrecedes(remote_slot->catalog_xmin,
+									  slot->data.catalog_xmin))
+			{
+				if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+				{
+					ReplicationSlotRelease();
+					return false;
+				}
+			}
+
+			/*
+			 * Wait for primary is over, update the lsns and mark the slot as
+			 * READY for further syncs.
+			 */
+			local_slot_update(remote_slot);
+			SpinLockAcquire(&slot->mutex);
+			slot->data.sync_state = SYNCSLOT_STATE_READY;
+			SpinLockRelease(&slot->mutex);
+
+			/* Save the changes */
+			ReplicationSlotMarkDirty();
+			ReplicationSlotSave();
+			slot_updated = true;
+
+			ereport(LOG,
+					errmsg("newly locally created slot \"%s\" is sync-ready now",
+						   remote_slot->name));
+		}
+		/* Slot ready for sync, so sync it. */
+		else if (sync_state == SYNCSLOT_STATE_READY)
+		{
+			/*
+			 * Sanity check: With hot_standby_feedback enabled and
+			 * invalidations handled appropriately as above, this should never
+			 * happen.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn)
+				elog(ERROR,
+					 "not synchronizing local slot \"%s\" LSN(%X/%X)"
+					 " to remote slot's LSN(%X/%X) as synchronization "
+					 " would move it backwards", remote_slot->name,
+					 LSN_FORMAT_ARGS(slot->data.restart_lsn),
+					 LSN_FORMAT_ARGS(remote_slot->restart_lsn));
+
+			if (remote_slot->confirmed_lsn != slot->data.confirmed_flush ||
+				remote_slot->restart_lsn != slot->data.restart_lsn ||
+				remote_slot->catalog_xmin != slot->data.catalog_xmin)
+			{
+				/* Update LSN of slot to remote slot's current position */
+				local_slot_update(remote_slot);
+				ReplicationSlotSave();
+				slot_updated = true;
+			}
+		}
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+
+		/* Skip creating the local slot if remote_slot is invalidated already */
+		if (remote_slot->invalidated != RS_INVAL_NONE)
+			return false;
+
+		/* Ensure that we have transaction env needed by get_database_oid() */
+		Assert(IsTransactionState());
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover,
+							  SYNCSLOT_STATE_INITIATED);
+
+		/* For shorter lines. */
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * Wait for the primary server to catch-up. Refer to the comment atop
+		 * the file for details on this wait.
+		 */
+		if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  slot->data.catalog_xmin))
+		{
+			bool		wait_attempts_exceeded = false;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &wait_attempts_exceeded))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid i.e wait_attempts_exceeded is true) and attempt
+				 * the wait and synchronization in the next cycle.
+				 */
+				if (wait_attempts_exceeded)
+				{
+					ReplicationSlotPersist();
+					slot_updated = true;
+				}
+
+				ReplicationSlotRelease();
+				return slot_updated;
+			}
+		}
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&slot->mutex);
+		slot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&slot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		slot_updated = true;
+
+		ereport(LOG,
+				errmsg("newly locally created slot \"%s\" is sync-ready now",
+					   remote_slot->name));
+	}
+
+	ReplicationSlotRelease();
+
+	return slot_updated;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and updates
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns TRUE if any of the slots gets updated in this sync-cycle.
+ */
+static bool
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *tupslot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	ListCell   *lc;
+	bool		some_slot_updated = false;
+
+	/*
+	 * The primary_slot_name is not set yet or WALs not received yet.
+	 * Synchronization is not possible if the walreceiver is not started.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		return false;
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	StartTransactionCommand();
+
+	initStringInfo(&s);
+
+	/* Construct query to fetch slots with failover enabled. */
+	appendStringInfo(&s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover");
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(tupslot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(tupslot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(tupslot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(tupslot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(tupslot, 5,
+																	   &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(tupslot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(tupslot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(tupslot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(tupslot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(tupslot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(lc, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		some_slot_updated |= synchronize_one_slot(wrconn, remote_slot);
+	}
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	CommitTransactionCommand();
+
+	return some_slot_updated;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker",
+							&err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Checks the primary server info.
+ *
+ * Using the specified primary server connection, check whether we are a
+ * cascading standby. It also validates primary_slot_name for non-cascading
+ * standbys.
+ */
+static void
+check_primary_info(WalReceiverConn *wrconn, bool *am_cascading_standby)
+{
+#define PRIMARY_INFO_OUTPUT_COL_COUNT 2
+	WalRcvExecResult *res;
+	Oid			slotRow[PRIMARY_INFO_OUTPUT_COL_COUNT] = {BOOLOID, BOOLOID};
+	StringInfoData cmd;
+	bool		isnull;
+	TupleTableSlot *tupslot;
+	bool		valid;
+	bool		remote_in_recovery;
+	bool		tuple_ok PG_USED_FOR_ASSERTS_ONLY;
+
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	StartTransactionCommand();
+
+	Assert(am_cascading_standby != NULL);
+
+	*am_cascading_standby = false;	/* overwritten later if cascading */
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT pg_is_in_recovery(), count(*) = 1"
+					 " FROM pg_replication_slots"
+					 " WHERE slot_type='physical' AND slot_name=%s",
+					 quote_literal_cstr(PrimarySlotName));
+
+	res = walrcv_exec(wrconn, cmd.data, PRIMARY_INFO_OUTPUT_COL_COUNT, slotRow);
+	pfree(cmd.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch primary_slot_name \"%s\" info from the "
+						"primary: %s", PrimarySlotName, res->err)));
+
+	tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	tuple_ok = tuplestore_gettupleslot(res->tuplestore, true, false, tupslot);
+	Assert(tuple_ok);			/* It must return one tuple */
+
+	remote_in_recovery = DatumGetBool(slot_getattr(tupslot, 1, &isnull));
+	Assert(!isnull);
+
+	/* No need to check further, return that we are cascading standby */
+	if (remote_in_recovery)
+	{
+		*am_cascading_standby = true;
+		ExecClearTuple(tupslot);
+		walrcv_clear_result(res);
+		CommitTransactionCommand();
+		return;
+	}
+
+	valid = DatumGetBool(slot_getattr(tupslot, 2, &isnull));
+	Assert(!isnull);
+
+	if (!valid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("exiting from slot synchronization due to bad configuration"),
+		/* translator: second %s is a GUC variable name */
+				 errdetail("The primary slot \"%s\" specified by %s is not valid.",
+						   PrimarySlotName, "primary_slot_name")));
+	ExecClearTuple(tupslot);
+	walrcv_clear_result(res);
+	CommitTransactionCommand();
+}
+
+/*
+ * Check that all necessary GUCs for slot synchronization are set
+ * appropriately. If not, raise an ERROR.
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+	/* Sanity check. */
+	Assert(enable_syncslot);
+
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_slot_name"));
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be enabled.", "hot_standby_feedback"));
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("wal_level must be >= logical."));
+
+	/*
+	 * The primary_conninfo is required to make connection to primary for
+	 * getting slots information.
+	 */
+	if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_conninfo"));
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	*dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (*dbname == NULL)
+		ereport(ERROR,
+
+		/*
+		 * translator: 'dbname' is a specific option; %s is a GUC variable
+		 * name
+		 */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("'dbname' must be specified in %s.", "primary_conninfo"));
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs have changed, exit the worker and
+ * let it get restarted by the postmaster.
+ */
+static void
+slotsync_reread_config(WalReceiverConn *wrconn)
+{
+	char	   *old_primary_conninfo = pstrdup(PrimaryConnInfo);
+	char	   *old_primary_slotname = pstrdup(PrimarySlotName);
+	bool		old_hot_standby_feedback = hot_standby_feedback;
+	bool		conninfo_changed;
+	bool		primary_slotname_changed;
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	conninfo_changed = strcmp(old_primary_conninfo, PrimaryConnInfo) != 0;
+	primary_slotname_changed = strcmp(old_primary_slotname, PrimarySlotName) != 0;
+
+	if (conninfo_changed ||
+		primary_slotname_changed ||
+		(old_hot_standby_feedback != hot_standby_feedback))
+	{
+		ereport(LOG,
+				errmsg("slot sync worker will restart because of "
+					   "a parameter change"));
+		/* The exit code 1 will make postmaster restart this worker */
+		proc_exit(1);
+	}
+
+	pfree(old_primary_conninfo);
+	pfree(old_primary_slotname);
+}
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		walrcv_disconnect(wrconn);
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting down "
+					   "on receiving SIGINT"));
+		proc_exit(0);
+	}
+
+	if (ConfigReloadPending)
+		slotsync_reread_config(wrconn);
+}
+
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = InvalidPid;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+	bool		am_cascading_standby;
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	Assert(SlotSyncWorker->pid == InvalidPid);
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	validate_slotsync_parameters(&dbname);
+
+	/*
+	 * Connect to the database specified by user in primary_conninfo. We need
+	 * a database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/*
+	 * Using the specified primary server connection, check whether we are
+	 * cascading standby and validates primary_slot_name for
+	 * non-cascading-standbys.
+	 */
+	check_primary_info(wrconn, &am_cascading_standby);
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+		TimestampTz now;
+		bool		some_slot_updated;
+
+		ProcessSlotSyncInterrupts(wrconn);
+
+		if (am_cascading_standby)
+		{
+			/*
+			 * Slot synchronization is currently not supported on cascading
+			 * standby. So if we are on the cascading standby, skip the sync
+			 * and take a longer nap before we check again whether we are
+			 * still cascading standby or not.
+			 */
+			naptime = 6 * WORKER_INACTIVITY_NAPTIME_MS; /* 60 sec */
+		}
+		else
+		{
+			some_slot_updated = synchronize_slots(wrconn);
+
+			/*
+			 * If any of the slots get updated in this sync-cycle, use default
+			 * naptime and update 'last_update_time'. But if no activity is
+			 * observed in this sync-cycle, then increase naptime provided
+			 * inactivity time reaches threshold.
+			 */
+			now = GetCurrentTimestamp();
+			if (some_slot_updated)
+				last_update_time = now;
+			else if (TimestampDifferenceExceeds(last_update_time,
+												now, WORKER_INACTIVITY_THRESHOLD_MS))
+				naptime = WORKER_INACTIVITY_NAPTIME_MS;
+		}
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+
+		/*
+		 * One can promote the standby and we can no longer be a cascading
+		 * standby. So recheck here.
+		 */
+		if (am_cascading_standby)
+			check_primary_info(wrconn, &am_cascading_standby);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsLogicalSlotSyncWorker(void)
+{
+	return SlotSyncWorker->pid == MyProcPid;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (SlotSyncWorker->pid == InvalidPid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Wait for it to die. */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long. */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (SlotSyncWorker->pid == InvalidPid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Allocate and initialize slot sync worker shared memory
+ */
+void
+SlotSyncWorkerShmemInit(void)
+{
+	Size		size;
+	bool		found;
+
+	size = sizeof(SlotSyncWorkerCtx);
+	size = MAXALIGN(size);
+
+	SlotSyncWorker = (SlotSyncWorkerCtx *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
+
+	if (!found)
+	{
+		memset(SlotSyncWorker, 0, size);
+		SlotSyncWorker->pid = InvalidPid;
+		SpinLockInit(&SlotSyncWorker->mutex);
+	}
+}
+
+/*
+ * Register the background worker for slots synchronization provided
+ * enable_syncslot is ON.
+ */
+void
+SlotSyncWorkerRegister(void)
+{
+	BackgroundWorker bgw;
+
+	if (!enable_syncslot)
+	{
+		ereport(LOG,
+				(errmsg("skipping slot synchronization"),
+				 errdetail("enable_syncslot is disabled.")));
+		return;
+	}
+
+	memset(&bgw, 0, sizeof(bgw));
+
+	/* We need database connection which needs shared-memory access as well. */
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+
+	/* Start as soon as a consistent state has been reached in a hot standby */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState_HotStandby;
+
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "replication slot sync worker");
+	snprintf(bgw.bgw_type, BGW_MAXLEN,
+			 "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
+	bgw.bgw_notify_pid = 0;
+	bgw.bgw_main_arg = (Datum) 0;
+
+	RegisterBackgroundWorker(&bgw);
+}
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e46a1955e8..7b3784c212 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -141,7 +141,20 @@
  * subscribe to the new primary without losing any data.
  *
  * However, we do not enable failover for slots created by the table sync
- * worker.
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby due to the following reasons:
+ *
+ * - The standby needs to wait for the primary server to catch up because the
+ *   local restart_lsn of the newly created slot on the standby is set using
+ *   the latest redo position (GetXLogReplayRecPtr()), which is typically ahead
+ *   of the primary's restart_lsn.
+ * - The table sync slot's restart_lsn won't be advanced until the state
+ *   becomes SUBREL_STATE_CATCHUP.
+ *
+ * Therefore, if a failover happens before the restart_lsn advances, the table
+ * sync slot will not be synced to the standby. Consequently, we will not be
+ * able to subscribe to the promoted standby due to the absence of the
+ * necessary table sync slot.
  *
  * Additionally, failover is not enabled for the main slot if the table sync is
  * in progress. This is because if a failover occurs while the table sync
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 49d2de5024..54a2b96418 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -262,16 +263,22 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     user will only get commit prepared.
  * failover: If enabled, allows the slot to be synced to physical standbys so
  *     that logical replication can be resumed after failover.
+ * sync_state: Defines slot synchronization state. This function is expected
+ *     to receive either SYNCSLOT_STATE_NONE for the user created slots or
+ *     SYNCSLOT_STATE_INITIATED for the slots being synchronized on the physical
+ *     standby.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover)
+					  bool two_phase, bool failover, char sync_state)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
 
 	Assert(MyReplicationSlot == NULL);
+	Assert(sync_state == SYNCSLOT_STATE_NONE ||
+		   sync_state == SYNCSLOT_STATE_INITIATED);
 
 	ReplicationSlotValidateName(name, ERROR);
 
@@ -327,6 +334,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = sync_state;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -686,12 +694,23 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary server.")));
+
 	ReplicationSlotDropAcquired();
 }
 
@@ -711,6 +730,17 @@ ReplicationSlotAlter(const char *name, bool failover)
 				errmsg("cannot use %s with a physical replication slot",
 					   "ALTER_REPLICATION_SLOT"));
 
+	/*
+	 * Do not allow users to alter the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot alter replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary server.")));
+
 	SpinLockAcquire(&MyReplicationSlot->mutex);
 	MyReplicationSlot->data.failover = failover;
 	SpinLockRelease(&MyReplicationSlot->mutex);
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index c87f61666d..e5ba5956aa 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -44,7 +44,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false);
+						  false, SYNCSLOT_STATE_NONE);
 
 	if (immediately_reserve)
 	{
@@ -137,7 +137,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover);
+						  failover, SYNCSLOT_STATE_NONE);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fddc9310c6..8d48d7c8f4 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1071,7 +1071,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false);
+							  false, false, SYNCSLOT_STATE_NONE);
 
 		if (reserve_wal)
 		{
@@ -1102,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover);
+							  two_phase, failover, SYNCSLOT_STATE_NONE);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, true);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0e0ac22bdd..ae2daff87b 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -37,6 +37,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -339,6 +340,7 @@ CreateOrAttachShmemStructs(void)
 	WalRcvShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7298a187d1..d87020821c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3286,6 +3286,17 @@ ProcessInterrupts(void)
 			 */
 			proc_exit(1);
 		}
+		else if (IsLogicalSlotSyncWorker())
+		{
+			elog(DEBUG1,
+				"replication slot sync worker is shutting down due to administrator command");
+
+			/*
+			 * Slot sync worker can be stopped at any time.
+			 * Use exit status 1 so the background worker is restarted.
+			 */
+			proc_exit(1);
+		}
 		else if (IsBackgroundWorker)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 9a54e04821..7d50971fd7 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2020,6 +2021,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_POSTMASTER, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5d940b72cd..39224137e1 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -358,6 +358,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = off			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bbc03bb76b..82a11e4a31 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index e90ff376a6..8559900b70 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -79,6 +79,7 @@ typedef enum
 	BgWorkerStart_PostmasterStart,
 	BgWorkerStart_ConsistentState,
 	BgWorkerStart_RecoveryFinished,
+	BgWorkerStart_ConsistentState_HotStandby,
 } BgWorkerStartTime;
 
 #define BGW_DEFAULT_RESTART_INTERVAL	60
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index bbd71d0b42..945d2608f6 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -22,6 +22,7 @@ extern void TablesyncWorkerMain(Datum main_arg);
 
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
+extern bool IsLogicalSlotSyncWorker(void);
 
 extern void HandleParallelApplyMessageInterrupt(void);
 extern void HandleParallelApplyMessages(void);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 5ddad69348..9e4389b991 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -52,6 +52,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +120,15 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Synchronization state for a logical slot.
+	 *
+	 * The standby can have any value among the possible values of 'i','r' and
+	 * 'n'. For primary, the default is 'n' for all slots but may also be 'r'
+	 * if leftover from a promoted standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)? Only
 	 * relevant for logical slots on the primary server.
@@ -225,9 +242,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover);
+								  bool two_phase, bool failover,
+								  char sync_state);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index f1135762fb..259d0f7065 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -279,6 +279,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -403,6 +418,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -428,6 +444,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 84bb79ac0f..9406a2666f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -237,6 +237,11 @@ extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
+/* Slot sync worker objects */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+extern PGDLLIMPORT bool enable_syncslot;
+
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
@@ -326,6 +331,12 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
+extern void SlotSyncWorkerRegister(void);
+extern void ShutDownSlotSync(void);
+extern void slotsync_drop_initiated_slots(void);
+extern void SlotSyncWorkerShmemInit(void);
+
 #define isParallelApplyWorker(worker) ((worker)->in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
 #define isTablesyncWorker(worker) ((worker)->in_use && \
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
index 5564577bbe..277fc2e079 100644
--- a/src/test/recovery/t/050_standby_failover_slots_sync.pl
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -296,5 +296,150 @@ is( $primary->safe_psql(
 	'logical slot has failover true on the primary');
 
 $subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
+$primary->safe_psql('postgres', "TRUNCATE tab_int");
+
+##################################################
+# Test logical failover slots on the standby
+# Configure standby1 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub1_slot->| ----> subscriber1 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb1_slot--->| ----> standby1 (connected via streaming replication)
+#                                        |                 lsub1_slot(synced_slot)
+##################################################
+
+my $connstr_1 = $primary->connstr;
+$standby1->stop;
+$standby1->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb1_slot'
+});
+$standby1->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+my $standby1_conninfo = $standby1->connstr . ' dbname=postgres';
+
+# Wait for the standby to start sync
+$offset = -s $standby1->logfile;
+$standby1->start;
+$standby1->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();
+	 SELECT pg_log_standby_snapshot();
+	 SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby1->logfile;
+$standby1->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby and is sync ready
+is($standby1->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}),
+	"t|r",
+	'logical slot has failover as true and sync_state as ready on standby');
+
+##################################################
+# Test that a synchronized slot can not be decoded, altered and dropped by the user
+##################################################
+
+# Disable hot_standby_feedback temporarily to stop slot sync worker otherwise
+# the concerned testing scenarios here may be interrupted by different error:
+# 'ERROR:  replication slot is active for PID ..'
+
+$standby1->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = off;');
+$standby1->restart;
+
+# Dropping of synced slot should result in error
+my ($result1, $stdout, $stderr) = $standby1->psql('postgres',
+	"SELECT pg_drop_replication_slot('lsub1_slot');");
+ok($stderr =~ /ERROR:  cannot drop replication slot "lsub1_slot"/,
+	"synced slot on standby cannot be dropped");
+
+# Logical decoding on synced slot should result in error
+($result1, $stdout, $stderr) = $standby1->psql('postgres',
+	"select * from pg_logical_slot_get_changes('lsub1_slot',NULL,NULL);");
+ok($stderr =~ /ERROR:  cannot use replication slot "lsub1_slot" for logical decoding/,
+	"logical decoding is not allowed on synced slot");
+
+($result, $stdout, $stderr) = $standby1->psql(
+    'postgres',
+    qq[ALTER_REPLICATION_SLOT lsub1_slot (failover);],
+    replication => 'database');
+ok($stderr =~ /ERROR:  cannot alter replication slot "lsub1_slot"/,
+	"synced slot on standby cannot be altered");
+
+# Enable hot_standby_feedback and restart standby
+$standby1->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = on;');
+$standby1->restart;
+
+##################################################
+# Create another slot which stays in sync_state as initiated ('i')
+# because it's a manually created slot and its lsn is not advanced.
+##################################################
+
+# Create a logical slot with failover = true
+$primary->psql('postgres',
+		q{SELECT pg_create_logical_replication_slot('logical_slot','pgoutput', false, true, true);});
+
+# Wait for the standby to start sync
+$offset = -s $standby1->logfile;
+$standby1->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"logical_slot\"/,
+	$offset);
+
+# Confirm that the logical slot is created on the standby and is in sync initiated state
+  is($standby1->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'logical_slot';}),
+	"t|i",
+	'logical slot has failover as true and sync_state as initiated on standby');
+
+##################################################
+# Promote the standby1 to primary. Confirm that:
+# a) the sync-ready('r') slot 'lsub1_slot' is retained on the new primary
+# b) the initiated('i') slot 'logical_slot' is dropped on promotion
+# c) logical replication for regress_mysub1 is resumed successfully after failover
+##################################################
+$standby1->promote;
+
+# Update subscription with the new primary's connection info
+$subscriber1->safe_psql('postgres',
+	"ALTER SUBSCRIPTION regress_mysub1 DISABLE;
+	 ALTER SUBSCRIPTION regress_mysub1 CONNECTION '$standby1_conninfo';
+	 ALTER SUBSCRIPTION regress_mysub1 ENABLE; ");
+
+is($standby1->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots WHERE slot_name in ('logical_slot','lsub1_slot');}),
+	'lsub1_slot',
+	'synced slot retained on the new primary');
+
+# Insert data on the new primary
+$primary_row_count = 10;
+$standby1->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+$standby1->wait_for_catchup('regress_mysub1');
+
+# Confirm that data in tab_int replicated on subscriber
+is( $subscriber1->safe_psql('postgres', q{SELECT count(*) FROM tab_int;}),
+	"$primary_row_count",
+	'data replicated from the new primary');
 
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 199863c6b5..10c6f32a30 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2321,6 +2321,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2579,6 +2580,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorkerCtx
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.34.1



  [application/octet-stream] v50-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (146.8K, ../../CAJpy0uB88wSeBiBb2eGsFusBmLVj7o_bJidViecf35wrktQc=A@mail.gmail.com/4-v50-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From 19d3456f856af1442d64440fe53c495ba0679fcf Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Wed, 6 Dec 2023 15:52:16 +0530
Subject: [PATCH v50 1/3] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to
the physical standbys so that logical replication can be resumed after
failover. It is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API.

Ex1:
CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

Ex2: (failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

A new replication command called ALTER_REPLICATION_SLOT and a
corresponding walreceiver API function named walrcv_alter_slot have been
implemented. They allow subscribers or users to modify the failover
property of a replication slot on the publisher.

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).
---
 contrib/test_decoding/expected/slot.out       |  58 +++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |  20 +-
 doc/src/sgml/ref/create_subscription.sgml     |  25 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       | 114 +++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  38 +-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  53 ++-
 src/backend/replication/logical/worker.c      |  67 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 192 +++++++++-
 src/backend/replication/slotfuncs.c           |  25 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 358 +++++++++++++++++-
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_dump/pg_dump.c                     |  20 +-
 src/bin/pg_dump/pg_dump.h                     |   1 +
 src/bin/pg_upgrade/info.c                     |   5 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 .../t/050_standby_failover_slots_sync.pl      | 300 +++++++++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 ++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 50 files changed, 1572 insertions(+), 170 deletions(-)
 create mode 100644 src/test/recovery/t/050_standby_failover_slots_sync.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 44cada2b40..7993fe3cdd 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4360,6 +4360,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..481e397bad 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
@@ -230,6 +233,17 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
+
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property of the new slot may differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription. When creating the slot,
+      ensure the slot failover property matches the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter value of the subscription.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..4d17e93a09 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,31 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state. It is the user's responsibility
+          to ensure that the initial table synchronization has been completed
+          before allowing the subscription to transition to the new primary.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..e68f0d401e 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,38 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If the slot_name is specified without the create_slot option,
+			 * it is possible that the user intends to use an existing slot on
+			 * the publisher, so here we alter the failover property of the
+			 * slot to match the failover value in subscription.
+			 *
+			 * We do not need to change the failover to false if the server
+			 * does not support failover (e.g. pre-PG17)
+			 */
+			else if (opts.slot_name &&
+					 (failover_enabled || walrcv_server_version(wrconn) >= 170000))
+			{
+				bool		failover_delayed = (!failover_enabled && opts.failover);
+
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+								opts.slot_name, failover_enabled ? "true" : "false"),
+						 failover_delayed ?
+						 errdetail("The failover state will be set to true once table synchronization has been completed.")
+						 : 0));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,13 +1335,22 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
@@ -1334,13 +1399,26 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 						/* translator: %s is an SQL ALTER command */
 								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
 										 isadd ?
@@ -1389,7 +1467,19 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 					ereport(ERROR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "two_phase"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "failover"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 693b3669ba..cf11b98da3 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -888,8 +892,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -918,7 +922,8 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+			appendStringInfoString(&cmd, "FAILOVER, ");
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -987,6 +992,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 4d056c16c8..7b6170fe55 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -624,15 +624,28 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "two_phase")));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "failover")));
+
 				should_exit = true;
 			}
 		}
@@ -1430,7 +1443,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1732,10 +1746,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1743,9 +1759,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1759,9 +1774,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..e46a1955e8 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,6 +132,33 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * failover = true when creating the subscription. Enabling failover allows us
+ * to smoothly transition to the promoted standby, ensuring that we can
+ * subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
@@ -3947,6 +3974,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4510,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4568,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4608,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..49d2de5024 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -248,10 +260,13 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     during getting changes, if the two_phase option is enabled it can skip
  *     prepare because by that time start decoding point has been moved. So the
  *     user will only get commit prepared.
+ * failover: If enabled, allows the slot to be synced to physical standbys so
+ *     that logical replication can be resumed after failover.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +695,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2200,148 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+	bool		ok;
+
+	/* Need a modifiable copy of string. */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into a list of identifiers. */
+	ok = SplitIdentifierString(rawname, ',', &elemlist);
+
+	if (!ok)
+		GUC_check_errdetail("List syntax is invalid.");
+
+	/*
+	 * If there is a syntax error in the name or if the replication slots'
+	 * data is not initialized yet (i.e., we are in the startup process), skip
+	 * the slot verification.
+	 */
+	if (!ok || !ReplicationSlotCtl)
+	{
+		pfree(rawname);
+		list_free(elemlist);
+		return ok;
+	}
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			GUC_check_errdetail("replication slot \"%s\" does not exist",
+								name);
+			ok = false;
+			break;
+		}
+
+		if (!SlotIsPhysical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			ok = false;
+			break;
+		}
+	}
+
+	pfree(rawname);
+	list_free(elemlist);
+	return ok;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	/*
+	 * Since we do not support syncing slots to cascading standbys, we return
+	 * NIL here if we are running in a standby to indicate that no standby
+	 * slots need to be waited for.
+	 */
+	if (RecoveryInProgress())
+		return NIL;
+
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..c87f61666d 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -491,6 +501,12 @@ pg_logical_replication_slot_advance(XLogRecPtr moveto)
 											   .segment_close = wal_segment_close),
 									NULL, NULL, NULL);
 
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to moveto lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(moveto);
+
 		/*
 		 * Start reading at the slot's restart_lsn, which we know to point to
 		 * a valid record.
@@ -679,6 +695,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +751,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +791,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 26ded928a7..ca61a99785 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -387,7 +387,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..fddc9310c6 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool		failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,257 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up the logical walsender processes with failover-enabled slots if the
+ * physical slot of the current walsender is specified in standby_slot_names
+ * GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names;
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	/*
+	 * If we are running on a standby, there is no need to reload
+	 * standby_slot_names since we do not support syncing slots to cascading
+	 * standbys.
+	 */
+	if (RecoveryInProgress())
+		return;
+
+	pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn. Additionally,
+ * it removes slots that have been invalidated, dropped, or converted to
+ * logical slots.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			/*
+			 * It may happen that the slot specified in standby_slot_names GUC
+			 * value is dropped, so let's skip over it.
+			 */
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+		}
+		else if (SlotIsLogical(slot))
+		{
+			/*
+			 * If a logical slot name is provided in standby_slot_names, issue
+			 * a WARNING and skip it. Although logical slots are disallowed in
+			 * the GUC check_hook(validate_standby_slots), it is still
+			 * possible for a user to drop an existing physical slot and
+			 * recreate a logical slot with the same name. Since it is
+			 * harmless, a WARNING should be enough, no need to error-out.
+			 */
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+		}
+		else
+		{
+			SpinLockAcquire(&slot->mutex);
+
+			if (slot->data.invalidated != RS_INVAL_NONE)
+			{
+				/*
+				 * Specified physical slot have been invalidated, so no point
+				 * in waiting for it.
+				 */
+				warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+			}
+			else if (XLogRecPtrIsInvalid(slot->data.restart_lsn) ||
+					 slot->data.restart_lsn < wait_for_lsn)
+			{
+				bool	inactive = (slot->active_pid == 0);
+
+				SpinLockRelease(&slot->mutex);
+
+				/* Log warning if no active_pid for this physical slot */
+				if (inactive)
+					ereport(WARNING,
+							errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+								   name, "standby_slot_names"),
+							errdetail("Logical replication is waiting on the "
+									  "standby associated with \"%s\".", name),
+							errhint("Consider starting standby associated with "
+									"\"%s\" or amend standby_slot_names.", name));
+
+				/* Continue if the current slot hasn't caught up. */
+				continue;
+			}
+			else
+			{
+				Assert(slot->data.restart_lsn >= wait_for_lsn);
+			}
+
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving the given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	if (standby_slots == NIL)
+		return;
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, we also
+ * wait for all the specified streaming replication standby servers to
+ * confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr even when we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
-	if (RecentFlushPtr != InvalidXLogRecPtr &&
-		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	if (!XLogRecPtrIsInvalid(RecentFlushPtr) && loc <= RecentFlushPtr)
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know
+		 * we have enough WAL available and all the standby servers have
+		 * confirmed receipt of WAL up to RecentFlushPtr. This is particularly
+		 * interesting if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1849,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1864,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,9 +1903,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
+		{
+			/* Already caught up and doesn't need to wait for standby_slots. */
 			break;
+		}
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
 		WalSndCaughtUp = true;
@@ -1654,9 +1954,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2121,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2358,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3621,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3692,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another
+	 * CV that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f7c9882f7c..9a54e04821 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4571,6 +4571,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..5d940b72cd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -329,6 +329,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8c0b5486b9..373c2514df 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4618,6 +4618,7 @@ getSubscriptions(Archive *fout)
 	int			i_subsynccommit;
 	int			i_subpublications;
 	int			i_suborigin;
+	int			i_subfailoverstate;
 	int			i,
 				ntups;
 
@@ -4673,14 +4674,22 @@ getSubscriptions(Archive *fout)
 		appendPQExpBufferStr(query,
 							 " s.subpasswordrequired,\n"
 							 " s.subrunasowner,\n"
-							 " s.suborigin\n");
+							 " s.suborigin,\n");
 	else
 		appendPQExpBuffer(query,
 						  " 't' AS subpasswordrequired,\n"
 						  " 't' AS subrunasowner,\n"
-						  " '%s' AS suborigin\n",
+						  " '%s' AS suborigin,\n",
 						  LOGICALREP_ORIGIN_ANY);
 
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(query,
+							 " s.subfailoverstate\n");
+	else
+		appendPQExpBuffer(query,
+						  " '%c' AS subfailoverstate\n",
+						  LOGICALREP_FAILOVER_STATE_DISABLED);
+
 	appendPQExpBufferStr(query,
 						 "FROM pg_subscription s\n"
 						 "WHERE s.subdbid = (SELECT oid FROM pg_database\n"
@@ -4709,6 +4718,7 @@ getSubscriptions(Archive *fout)
 	i_subsynccommit = PQfnumber(res, "subsynccommit");
 	i_subpublications = PQfnumber(res, "subpublications");
 	i_suborigin = PQfnumber(res, "suborigin");
+	i_subfailoverstate = PQfnumber(res, "subfailoverstate");
 
 	subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo));
 
@@ -4746,6 +4756,8 @@ getSubscriptions(Archive *fout)
 		subinfo[i].subpublications =
 			pg_strdup(PQgetvalue(res, i, i_subpublications));
 		subinfo[i].suborigin = pg_strdup(PQgetvalue(res, i, i_suborigin));
+		subinfo[i].subfailoverstate =
+			pg_strdup(PQgetvalue(res, i, i_subfailoverstate));
 
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(subinfo[i].dobj), fout);
@@ -4771,6 +4783,7 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	int			npubnames = 0;
 	int			i;
 	char		two_phase_disabled[] = {LOGICALREP_TWOPHASE_STATE_DISABLED, '\0'};
+	char		failover_disabled[] = {LOGICALREP_FAILOVER_STATE_DISABLED, '\0'};
 
 	/* Do nothing in data-only dump */
 	if (dopt->dataOnly)
@@ -4818,6 +4831,9 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	if (strcmp(subinfo->subtwophasestate, two_phase_disabled) != 0)
 		appendPQExpBufferStr(query, ", two_phase = on");
 
+	if (strcmp(subinfo->subfailoverstate, failover_disabled) != 0)
+		appendPQExpBufferStr(query, ", failover = true");
+
 	if (strcmp(subinfo->subdisableonerr, "t") == 0)
 		appendPQExpBufferStr(query, ", disable_on_error = true");
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2fe3cbed9a..f62a4dfd4b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -671,6 +671,7 @@ typedef struct _SubscriptionInfo
 	char	   *subsynccommit;
 	char	   *subpublications;
 	char	   *suborigin;
+	char	   *subfailoverstate;
 } SubscriptionInfo;
 
 /*
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..e16286f18c 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,6 +679,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
@@ -687,6 +688,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +699,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..2d8bcb26f9 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced to the
+								 * physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 020e7aa1cc..cb3a2b3aed 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -159,7 +159,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -179,8 +179,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 049801186c..905964a2e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3327,7 +3327,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 77e8b13764..bbc03bb76b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..5ddad69348 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)? Only
+	 * relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 949e874f21..f1135762fb 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -355,9 +355,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									  const char *slotname,
+									  bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -399,6 +410,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -428,8 +440,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 60313980a9..ecd212c7b6 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -12,6 +12,8 @@
 #ifndef _WALSENDER_H
 #define _WALSENDER_H
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -45,6 +47,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index db73408937..84bb79ac0f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -256,7 +256,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..083b558448 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_standby_failover_slots_sync.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
new file mode 100644
index 0000000000..5564577bbe
--- /dev/null
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -0,0 +1,300 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+##################################################
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+##################################################
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on the primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres',
+	"CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+
+# Create a table and a subscription with failover = true
+$subscriber1->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);
+]);
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	CREATE SUBSCRIPTION regress_mysub2 CONNECTION '$publisher_connstr' PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);
+]);
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with the specified physical replication slot so
+# that the logical replication slot won't receive changes until the standby
+# comes up.
+$standby1->stop;
+
+# Create some data on the primary
+my $primary_row_count   = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('regress_mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary until standby1 acknowledges changes"
+);
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 acknowledges changes");
+
+# Stop the standby associated with the specified physical replication slot so
+# that the logical replication slot won't receive changes until the standby
+# slot's restart_lsn is advanced or the slot is removed from the
+# standby_slot_names list
+$publisher->safe_psql('postgres', "TRUNCATE tab_int;");
+$publisher->wait_for_catchup('regress_mysub1');
+$standby1->stop;
+
+##################################################
+# Verify that when using pg_logical_slot_get_changes to consume changes from a
+# logical slot with failover enabled, it will also wait for the slots specified
+# in standby_slot_names to catch up.
+##################################################
+
+# Create a logical 'test_decoding' replication slot with failover enabled
+$publisher->safe_psql('postgres',
+	"SELECT pg_create_logical_replication_slot('test_slot', 'test_decoding', false, false, true);"
+);
+
+my $back_q = $primary->background_psql('postgres', on_error_stop => 0);
+my $pid = $back_q->query('SELECT pg_backend_pid()');
+
+# Try and get changes from the logical slot with failover enabled.
+my $offset = -s $primary->logfile;
+$back_q->query_until(qr//,
+	"SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL);\n");
+
+# Wait until the primary server logs a warning indicating that it is waiting
+# for the sb1_slot to catch up.
+$primary->wait_for_log(
+	qr/WARNING: ( [A-Z0-9]+:)? replication slot \"sb1_slot\" specified in parameter \"standby_slot_names\" does not have active_pid/,
+	$offset);
+
+ok($primary->safe_psql('postgres', "SELECT pg_cancel_backend($pid)"),
+	"cancelling pg_logical_slot_get_changes command");
+
+$publisher->safe_psql('postgres',
+	"SELECT pg_drop_replication_slot('test_slot');"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we manually advance this slot's LSN to the
+# latest position.
+##################################################
+
+# Create some data on the primary
+$primary_row_count = 10;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# The subscription that's up and running and is enabled for failover doesn't
+# get the data from primary and keeps waiting for the standby specified in
+# standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary as long as standby1 restart_lsn has not been updated"
+);
+
+# Advance the lsn of the standby slot manually
+my $lsn = $primary->lsn('write');
+$primary->safe_psql('postgres',
+	"SELECT * FROM pg_replication_slot_advance('sb1_slot', '$lsn');"
+);
+
+# Now that the standby lsn has advanced, primary must send the decoded
+# changes to the subscription
+$publisher->wait_for_catchup('regress_mysub1', 'replay', $lsn);
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1's restart_lsn has been updated"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we remove the slot from standby_slot_names.
+##################################################
+
+# Create some data on the primary
+$primary_row_count = 20;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(11,20);");
+
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 10 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data as the sb1_slot doesn't catch up");
+
+# Remove the standby from the standby_slot_names list and reload the
+# configuration
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', "''");
+$primary->psql('postgres', "SELECT pg_reload_conf()");
+
+# Now that the standby lsn has advanced, the primary must send the decoded
+# changes to the subscription.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 is removed from the standby_slot_names list"
+);
+
+# Put the standby back on the primary_slot_name for the rest of the tests
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', 'sb1_slot');
+$primary->restart();
+
+##################################################
+# Test that when a subscription with failover enabled is created, it will alter
+# the failover property of the corresponding slot on the publisher.
+##################################################
+
+# Create a slot on the publisher with failover disabled
+$primary->safe_psql('postgres',
+	"SELECT 'init' FROM pg_create_logical_replication_slot('lsub3_slot', 'pgoutput', false, false, false);"
+);
+
+# Confirm that the failover flag on the slot is turned off
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"f",
+	'logical slot has failover false on the primary');
+
+# Create another subscription (using the same slot created above) that enables
+# failover.
+$subscriber1->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub3 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub3_slot, copy_data=false, failover = true, create_slot = false);"
+);
+
+# Confirm that the failover flag on the slot has now been turned on
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"t",
+	'logical slot has failover true on the primary');
+
+$subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ba41149b88..199863c6b5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3870,6 +3871,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-18 23:20                                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-19 06:06                                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-19 11:40                                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-19 13:05                                                                     ` Hayato Kuroda (Fujitsu) <[email protected]>
  2023-12-20 05:52                                                                       ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-20 12:43                                                                       ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  1 sibling, 2 replies; 113+ messages in thread

From: Hayato Kuroda (Fujitsu) @ 2023-12-19 13:05 UTC (permalink / raw)
  To: 'shveta malik' <[email protected]>; Amit Kapila <[email protected]>; +Cc: Peter Smith <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Dear Shveta,

I resumed to review the patch. I will play more about it, but I can post some
cosmetic comments.

====
walsender.c

01. WalSndWaitForStandbyConfirmation

```
+        sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
```

It works well, but I'm not sure whether we should use WalSndComputeSleeptime()
because the function won't be called by walsender.

02.WalSndWaitForStandbyConfirmation

```
+        ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+                                    WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
```

Hmm, is it OK to use the same event as WalSndWaitForWal()? IIUC it should be avoided.

03. WalSndShmemInit()

```
+
+        ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
```

Unnecessary blank?

~~~~~
050_standby_failover_slots_sync.pl

04. General

My pgperltidy modified your test. Please check.

05.

```
# Create publication on the primary
```

Missing "a" before publication?

06.

```
$subscriber1->init(allows_streaming => 'logical');
...
$subscriber2->init(allows_streaming => 'logical');
```

IIUC, these settings are not needed.

07.

```
my $primary_insert_time = time();
```

The variable is not used.

08.

```
# Stop the standby associated with the specified physical replication slot so
# that the logical replication slot won't receive changes until the standby
# slot's restart_lsn is advanced or the slot is removed from the
# standby_slot_names list
```

Missing comma?

09.

```
$back_q->query_until(qr//,
	"SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL);\n");
```

Not sure, should we have to close the back_q connection?

10.

```
# Remove the standby from the standby_slot_names list and reload the
# configuration
$primary->adjust_conf('postgresql.conf', 'standby_slot_names', "''");
$primary->psql('postgres', "SELECT pg_reload_conf()");
```

a.
Missing comma?

b.
I counted and reload function in perl (e.g., `$primary->reload;`) is more often to
be used. Do you have a reason to use pg_reload_conf()?

11.

```
# Now that the standby lsn has advanced, the primary must send the decoded
# changes to the subscription.
$publisher->wait_for_catchup('regress_mysub1');
```

Is the comment correct? I think primary sends data because the GUC is modified.

12.

```
# Put the standby back on the primary_slot_name for the rest of the tests
$primary->adjust_conf('postgresql.conf', 'standby_slot_names', 'sb1_slot');
$primary->restart();
```

Just to confirm - you used restart() here because we must ensure the GUC change is
propagated to all backends, right?

~~~~~
wait_event_names.txt

13.

```
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION    "Waiting for the WAL to be received by physical standby in WAL sender process."
```

But there is a possibility that backend processes may wait with the event, right?

Best Regards,
Hayato Kuroda
FUJITSU LIMITED



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-18 23:20                                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-19 06:06                                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-19 11:40                                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-19 13:05                                                                     ` RE: Synchronizing slots from primary to standby Hayato Kuroda (Fujitsu) <[email protected]>
@ 2023-12-20 05:52                                                                       ` Amit Kapila <[email protected]>
  2023-12-20 08:03                                                                         ` RE: Synchronizing slots from primary to standby Hayato Kuroda (Fujitsu) <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Amit Kapila @ 2023-12-20 05:52 UTC (permalink / raw)
  To: Hayato Kuroda (Fujitsu) <[email protected]>; +Cc: shveta malik <[email protected]>; Peter Smith <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Tue, Dec 19, 2023 at 6:35 PM Hayato Kuroda (Fujitsu)
<[email protected]> wrote:
>
> ====
> walsender.c
>
> 01. WalSndWaitForStandbyConfirmation
>
> ```
> +        sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
> ```
>
> It works well, but I'm not sure whether we should use WalSndComputeSleeptime()
> because the function won't be called by walsender.
>

I don't think it is correct to use this function because it is
walsender specific, for example, it uses 'last_reply_timestamp' which
won't be even initialized in the backend environment. We need to
probably use a different logic for sleep here or need to use a
hard-coded value. I think we should change the name of functions like
WalSndWaitForStandbyConfirmation() as they are no longer used by
walsender. IIRC, earlier, we had a common logic to wait from both
walsender and SQL APIs which led to this naming but that is no longer
true with the latest patch.

> 02.WalSndWaitForStandbyConfirmation
>
> ```
> +        ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
> +                                    WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
> ```
>
> Hmm, is it OK to use the same event as WalSndWaitForWal()? IIUC it should be avoided.
>

Agreed. So, how about using WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION
so that we can use it both from the backend and walsender?

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-18 23:20                                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-19 06:06                                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-19 11:40                                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-19 13:05                                                                     ` RE: Synchronizing slots from primary to standby Hayato Kuroda (Fujitsu) <[email protected]>
  2023-12-20 05:52                                                                       ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-12-20 08:03                                                                         ` Hayato Kuroda (Fujitsu) <[email protected]>
  2023-12-20 12:42                                                                           ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Hayato Kuroda (Fujitsu) @ 2023-12-20 08:03 UTC (permalink / raw)
  To: 'Amit Kapila' <[email protected]>; shveta malik <[email protected]>; +Cc: Peter Smith <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Dear Amit, Shveta,

> > walsender.c
> >
> > 01. WalSndWaitForStandbyConfirmation
> >
> > ```
> > +        sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
> > ```
> >
> > It works well, but I'm not sure whether we should use
> WalSndComputeSleeptime()
> > because the function won't be called by walsender.
> >
> 
> I don't think it is correct to use this function because it is
> walsender specific, for example, it uses 'last_reply_timestamp' which
> won't be even initialized in the backend environment. We need to
> probably use a different logic for sleep here or need to use a
> hard-coded value.

Oh, you are right. I haven't look until the func.

> I think we should change the name of functions like
> WalSndWaitForStandbyConfirmation() as they are no longer used by
> walsender. IIRC, earlier, we had a common logic to wait from both
> walsender and SQL APIs which led to this naming but that is no longer
> true with the latest patch.

How about "WaitForStandbyConfirmation", which is simpler? There are some
functions like "WaitForParallelWorkersToFinish", "WaitForProcSignalBarrier" and so on.

> > 02.WalSndWaitForStandbyConfirmation
> >
> > ```
> > +        ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv,
> sleeptime,
> > +
> WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
> > ```
> >
> > Hmm, is it OK to use the same event as WalSndWaitForWal()? IIUC it should be
> avoided.
> >
> 
> Agreed. So, how about using
> WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION
> so that we can use it both from the backend and walsender?

Seems right. Note again that a description of .txt file must be also fixed.

Anyway, further comments on v50-0001.

~~~~~
protocol.sgml

01. create_replication_slot

```
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
```

IIUC, the true/false is optional. libpqwalreceiver does not add the boolean.
Also you can follow the notation of `TWO_PHASE`.

02. alter_replication_slot

```
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
```

Apart from above, this boolean is mandatory, right?
But you can follow other notation.


~~~~~~~
slot.c

03. validate_standby_slots

```
+    /* Need a modifiable copy of string. */
...
+    /* Verify syntax and parse string into a list of identifiers. */
```

Unnecessary comma?

04. validate_standby_slots

```
+    if (!ok || !ReplicationSlotCtl)
+    {
+        pfree(rawname);
+        list_free(elemlist);
+        return ok;
+    }
```

It may be more efficient to exit earlier when ReplicationSlotCtl is NULL.

~~~~~~~
walsender.c

05. PhysicalWakeupLogicalWalSnd

```
+/*
+ * Wake up the logical walsender processes with failover-enabled slots if the
+ * physical slot of the current walsender is specified in standby_slot_names
+ * GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
```

The function can be called from backend processes, but you said "the current walsender"
in the comment.

06. WalSndRereadConfigAndReInitSlotList

```
+    char       *pre_standby_slot_names;
+
+    ProcessConfigFile(PGC_SIGHUP);
+
+    /*
+     * If we are running on a standby, there is no need to reload
+     * standby_slot_names since we do not support syncing slots to cascading
+     * standbys.
+     */
+    if (RecoveryInProgress())
+        return;
+
+    pre_standby_slot_names = pstrdup(standby_slot_names);
```

I felt that we must preserve pre_standby_slot_names before calling ProcessConfigFile().


07. WalSndFilterStandbySlots

I felt the prefix "WalSnd" may not be needed because both backend processes and
walsender will call the function.

Best Regards,
Hayato Kuroda
FUJITSU LIMITED



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-18 23:20                                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-19 06:06                                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-19 11:40                                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-19 13:05                                                                     ` RE: Synchronizing slots from primary to standby Hayato Kuroda (Fujitsu) <[email protected]>
  2023-12-20 05:52                                                                       ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-20 08:03                                                                         ` RE: Synchronizing slots from primary to standby Hayato Kuroda (Fujitsu) <[email protected]>
@ 2023-12-20 12:42                                                                           ` Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-21 02:23                                                                             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-21 04:24                                                                             ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  0 siblings, 2 replies; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-12-20 12:42 UTC (permalink / raw)
  To: Hayato Kuroda (Fujitsu) <[email protected]>; 'Amit Kapila' <[email protected]>; shveta malik <[email protected]>; +Cc: Peter Smith <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Wednesday, December 20, 2023 4:03 PM Kuroda, Hayato/é»’ç”° 隼人 <[email protected]> wrote:

Hi,

> 
> Dear Amit, Shveta,
> 
> > > walsender.c
> > >
> > > 01. WalSndWaitForStandbyConfirmation
> > >
> > > ```
> > > +        sleeptime =
> WalSndComputeSleeptime(GetCurrentTimestamp());
> > > ```
> > >
> > > It works well, but I'm not sure whether we should use
> > WalSndComputeSleeptime()
> > > because the function won't be called by walsender.
> > >
> >
> > I don't think it is correct to use this function because it is
> > walsender specific, for example, it uses 'last_reply_timestamp' which
> > won't be even initialized in the backend environment. We need to
> > probably use a different logic for sleep here or need to use a
> > hard-coded value.
> 
> Oh, you are right. I haven't look until the func.
> 
> > I think we should change the name of functions like
> > WalSndWaitForStandbyConfirmation() as they are no longer used by
> > walsender. IIRC, earlier, we had a common logic to wait from both
> > walsender and SQL APIs which led to this naming but that is no longer
> > true with the latest patch.
> 
> How about "WaitForStandbyConfirmation", which is simpler? There are some
> functions like "WaitForParallelWorkersToFinish", "WaitForProcSignalBarrier"
> and so on.

Thanks for the comments. I think WaitForStandbyConfirmation is OK.
And I removed the WalSnd prefix for these functions and move them to
slot.c where the standby_slot_names is declared.

> 
> > > 02.WalSndWaitForStandbyConfirmation
> > >
> > > ```
> > > +        ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv,
> > sleeptime,
> > > +
> > WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
> > > ```
> > >
> > > Hmm, is it OK to use the same event as WalSndWaitForWal()? IIUC it
> > > should be
> > avoided.
> > >
> >
> > Agreed. So, how about using
> > WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION
> > so that we can use it both from the backend and walsender?
> 
> Seems right. Note again that a description of .txt file must be also fixed.

Changed.

> 
> Anyway, further comments on v50-0001.
> 
> ~~~~~
> protocol.sgml
> 
> 01. create_replication_slot
> 
> ```
> +       <varlistentry>
> +        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
> +        <listitem>
> +         <para>
> +          If true, the slot is enabled to be synced to the physical
> +          standbys so that logical replication can be resumed after failover.
> +         </para>
> +        </listitem>
> +       </varlistentry>
> ```
> 
> IIUC, the true/false is optional. libpqwalreceiver does not add the boolean.
> Also you can follow the notation of `TWO_PHASE`.

Changed.

> 
> 02. alter_replication_slot
> 
> ```
> +      <variablelist>
> +       <varlistentry>
> +        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
> +        <listitem>
> +         <para>
> +          If true, the slot is enabled to be synced to the physical
> +          standbys so that logical replication can be resumed after failover.
> +         </para>
> +        </listitem>
> +       </varlistentry>
> +      </variablelist>
> ```
> 
> Apart from above, this boolean is mandatory, right?
> But you can follow other notation.
> 

Right, changed it to optional to be consistent with others.

> 
> ~~~~~~~
> slot.c
> 
> 03. validate_standby_slots
> 
> ```
> +    /* Need a modifiable copy of string. */
> ...
> +    /* Verify syntax and parse string into a list of identifiers. */
> ```
> 
> Unnecessary comma?

You mean comma or period ? I think the current style is OK.

> 
> 04. validate_standby_slots
> 
> ```
> +    if (!ok || !ReplicationSlotCtl)
> +    {
> +        pfree(rawname);
> +        list_free(elemlist);
> +        return ok;
> +    }
> ```
> 
> It may be more efficient to exit earlier when ReplicationSlotCtl is NULL.

I think even if ReplicationSlotCtl is NULL, we still need to check the syntax
of the slot names.

> 
> ~~~~~~~
> walsender.c
> 
> 05. PhysicalWakeupLogicalWalSnd
> 
> ```
> +/*
> + * Wake up the logical walsender processes with failover-enabled slots
> +if the
> + * physical slot of the current walsender is specified in
> +standby_slot_names
> + * GUC.
> + */
> +void
> +PhysicalWakeupLogicalWalSnd(void)
> ```
> 
> The function can be called from backend processes, but you said "the current
> walsender"
> in the comment.

Changed the words.

> 
> 06. WalSndRereadConfigAndReInitSlotList
> 
> ```
> +    char       *pre_standby_slot_names;
> +
> +    ProcessConfigFile(PGC_SIGHUP);
> +
> +    /*
> +     * If we are running on a standby, there is no need to reload
> +     * standby_slot_names since we do not support syncing slots to
> cascading
> +     * standbys.
> +     */
> +    if (RecoveryInProgress())
> +        return;
> +
> +    pre_standby_slot_names = pstrdup(standby_slot_names);
> ```
> 
> I felt that we must preserve pre_standby_slot_names before calling
> ProcessConfigFile().
> 

Good catch. Fixed.

> 
> 07. WalSndFilterStandbySlots
> 
> I felt the prefix "WalSnd" may not be needed because both backend processes
> and walsender will call the function.

Right, renamed.

Attach the V51 patch set which addressed Kuroda-san's comments.
I also tried to improve the test in 0003 to make it stable.

Best Regards,
Hou zj


Attachments:

  [application/octet-stream] v51-0003-Additional-test-to-validate-the-restart_lsn-of-s.patch (3.6K, ../../OS0PR01MB57166472DB3D01F7407259179496A@OS0PR01MB5716.jpnprd01.prod.outlook.com/2-v51-0003-Additional-test-to-validate-the-restart_lsn-of-s.patch)
  download | inline diff:
From a541275375cb27a18807f7434fcf5521e7d37d7b Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Tue, 19 Dec 2023 16:31:49 +0530
Subject: [PATCH v51 3/3] Additional test to validate the restart_lsn of synced
 slot

---
 .../t/050_standby_failover_slots_sync.pl      | 47 +++++++++++++++++--
 1 file changed, 42 insertions(+), 5 deletions(-)

diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
index f53c69daba..dae10d67c0 100644
--- a/src/test/recovery/t/050_standby_failover_slots_sync.pl
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -358,6 +358,45 @@ is($standby1->safe_psql('postgres',
 	"t|r",
 	'logical slot has failover as true and sync_state as ready on standby');
 
+##################################################
+# Test to confirm that restart_lsn and confirmed_flush_lsn of the logical slot
+# on the primary is synced to the standby
+##################################################
+
+# Insert data on the primary
+$primary->safe_psql(
+	'postgres', qq[
+	TRUNCATE TABLE tab_int;
+	INSERT INTO tab_int SELECT generate_series(1, 10);
+]);
+
+$primary->wait_for_catchup('regress_mysub1');
+
+# Do not allow any further advancement of the restart_lsn and
+# confirmed_flush_lsn for the lsub1_slot.
+$subscriber1->safe_psql('postgres', "ALTER SUBSCRIPTION regress_mysub1 DISABLE");
+
+# Wait for the replication slot to become inactive on the publisher
+$primary->poll_query_until(
+	'postgres',
+	"SELECT COUNT(*) FROM pg_catalog.pg_replication_slots WHERE slot_name = 'lsub1_slot' AND active='f'",
+	1);
+
+# Get the restart_lsn for the logical slot lsub1_slot on the primary
+my $primary_restart_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Get the confirmed_flush_lsn for the logical slot lsub1_slot on the primary
+my $primary_flush_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that restart_lsn and of confirmed_flush_lsn lsub1_slot slot are synced
+# to the standby
+ok( $standby1->poll_query_until(
+		'postgres',
+		"SELECT '$primary_restart_lsn' = restart_lsn AND '$primary_flush_lsn' = confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';"),
+	'restart_lsn and confirmed_flush_lsn of slot lsub1_slot synced to standby');
+
 ##################################################
 # Test that a synchronized slot can not be decoded, altered and dropped by the user
 ##################################################
@@ -423,8 +462,7 @@ $standby1->promote;
 
 # Update subscription with the new primary's connection info
 $subscriber1->safe_psql('postgres',
-	"ALTER SUBSCRIPTION regress_mysub1 DISABLE;
-	 ALTER SUBSCRIPTION regress_mysub1 CONNECTION '$standby1_conninfo';
+	"ALTER SUBSCRIPTION regress_mysub1 CONNECTION '$standby1_conninfo';
 	 ALTER SUBSCRIPTION regress_mysub1 ENABLE; ");
 
 is($standby1->safe_psql('postgres',
@@ -433,14 +471,13 @@ is($standby1->safe_psql('postgres',
 	'synced slot retained on the new primary');
 
 # Insert data on the new primary
-$primary_row_count = 10;
 $standby1->safe_psql('postgres',
-	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+	"INSERT INTO tab_int SELECT generate_series(11, 20);");
 $standby1->wait_for_catchup('regress_mysub1');
 
 # Confirm that data in tab_int replicated on subscriber
 is( $subscriber1->safe_psql('postgres', q{SELECT count(*) FROM tab_int;}),
-	"$primary_row_count",
+	"20",
 	'data replicated from the new primary');
 
 done_testing();
-- 
2.30.0.windows.2



  [application/octet-stream] v51-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (147.2K, ../../OS0PR01MB57166472DB3D01F7407259179496A@OS0PR01MB5716.jpnprd01.prod.outlook.com/3-v51-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From ea1477199e2b4a2f55f31c383fe5783859bac32a Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Wed, 6 Dec 2023 15:52:16 +0530
Subject: [PATCH v51 1/3] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to
the physical standbys so that logical replication can be resumed after
failover. It is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API.

Ex1:
CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

Ex2: (failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

A new replication command called ALTER_REPLICATION_SLOT and a
corresponding walreceiver API function named walrcv_alter_slot have been
implemented. They allow subscribers or users to modify the failover
property of a replication slot on the publisher.

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).
---
 contrib/test_decoding/expected/slot.out       |  58 +++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |  20 +-
 doc/src/sgml/ref/create_subscription.sgml     |  25 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       | 114 +++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  38 +-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  53 ++-
 src/backend/replication/logical/worker.c      |  67 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 375 +++++++++++++++++-
 src/backend/replication/slotfuncs.c           |  25 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 178 ++++++++-
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_dump/pg_dump.c                     |  20 +-
 src/bin/pg_dump/pg_dump.h                     |   1 +
 src/bin/pg_upgrade/info.c                     |   5 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  16 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   3 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 .../t/050_standby_failover_slots_sync.pl      | 301 ++++++++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 ++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 50 files changed, 1579 insertions(+), 170 deletions(-)
 create mode 100644 src/test/recovery/t/050_standby_failover_slots_sync.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 44cada2b40..7993fe3cdd 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4360,6 +4360,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bef18fcd47 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER [ <replaceable class="parameter">boolean</replaceable> ]</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER [ <replaceable class="parameter">boolean</replaceable> ]</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..481e397bad 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
@@ -230,6 +233,17 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
+
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property of the new slot may differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription. When creating the slot,
+      ensure the slot failover property matches the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter value of the subscription.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..4d17e93a09 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,31 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state. It is the user's responsibility
+          to ensure that the initial table synchronization has been completed
+          before allowing the subscription to transition to the new primary.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..e68f0d401e 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,38 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If the slot_name is specified without the create_slot option,
+			 * it is possible that the user intends to use an existing slot on
+			 * the publisher, so here we alter the failover property of the
+			 * slot to match the failover value in subscription.
+			 *
+			 * We do not need to change the failover to false if the server
+			 * does not support failover (e.g. pre-PG17)
+			 */
+			else if (opts.slot_name &&
+					 (failover_enabled || walrcv_server_version(wrconn) >= 170000))
+			{
+				bool		failover_delayed = (!failover_enabled && opts.failover);
+
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+								opts.slot_name, failover_enabled ? "true" : "false"),
+						 failover_delayed ?
+						 errdetail("The failover state will be set to true once table synchronization has been completed.")
+						 : 0));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,13 +1335,22 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
@@ -1334,13 +1399,26 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 						/* translator: %s is an SQL ALTER command */
 								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
 										 isadd ?
@@ -1389,7 +1467,19 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 					ereport(ERROR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "two_phase"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "failover"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 693b3669ba..cf11b98da3 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -888,8 +892,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -918,7 +922,8 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+			appendStringInfoString(&cmd, "FAILOVER, ");
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -987,6 +992,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..330a55d35d 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 4d056c16c8..7b6170fe55 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -624,15 +624,28 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "two_phase")));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "failover")));
+
 				should_exit = true;
 			}
 		}
@@ -1430,7 +1443,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1732,10 +1746,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1743,9 +1759,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1759,9 +1774,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..e46a1955e8 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,6 +132,33 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * failover = true when creating the subscription. Enabling failover allows us
+ * to smoothly transition to the promoted standby, ensuring that we can
+ * subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
@@ -3947,6 +3974,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4510,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4568,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4608,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..8fb0578cda 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -46,12 +46,17 @@
 #include "common/string.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "postmaster/interrupt.h"
 #include "replication/slot.h"
+#include "replication/walsender_private.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +95,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +103,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -248,10 +262,13 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     during getting changes, if the two_phase option is enabled it can skip
  *     prepare because by that time start decoding point has been moved. So the
  *     user will only get commit prepared.
+ * failover: If enabled, allows the slot to be synced to physical standbys so
+ *     that logical replication can be resumed after failover.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +328,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +697,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2202,329 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+	bool		ok;
+
+	/* Need a modifiable copy of string. */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into a list of identifiers. */
+	ok = SplitIdentifierString(rawname, ',', &elemlist);
+
+	if (!ok)
+		GUC_check_errdetail("List syntax is invalid.");
+
+	/*
+	 * If there is a syntax error in the name or if the replication slots'
+	 * data is not initialized yet (i.e., we are in the startup process), skip
+	 * the slot verification.
+	 */
+	if (!ok || !ReplicationSlotCtl)
+	{
+		pfree(rawname);
+		list_free(elemlist);
+		return ok;
+	}
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			GUC_check_errdetail("replication slot \"%s\" does not exist",
+								name);
+			ok = false;
+			break;
+		}
+
+		if (!SlotIsPhysical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			ok = false;
+			break;
+		}
+	}
+
+	pfree(rawname);
+	list_free(elemlist);
+	return ok;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	/*
+	 * Since we do not support syncing slots to cascading standbys, we return
+	 * NIL here if we are running in a standby to indicate that no standby
+	 * slots need to be waited for.
+	 */
+	if (RecoveryInProgress())
+		return NIL;
+
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+void
+RereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names;
+
+	/*
+	 * If we are running on a standby, there is no need to reload
+	 * standby_slot_names since we do not support syncing slots to cascading
+	 * standbys.
+	 */
+	if (RecoveryInProgress())
+	{
+		ProcessConfigFile(PGC_SIGHUP);
+		return;
+	}
+
+	pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn. Additionally,
+ * it removes slots that have been invalidated, dropped, or converted to
+ * logical slots.
+ */
+void
+FilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			/*
+			 * It may happen that the slot specified in standby_slot_names GUC
+			 * value is dropped, so let's skip over it.
+			 */
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+		}
+		else if (SlotIsLogical(slot))
+		{
+			/*
+			 * If a logical slot name is provided in standby_slot_names, issue
+			 * a WARNING and skip it. Although logical slots are disallowed in
+			 * the GUC check_hook(validate_standby_slots), it is still
+			 * possible for a user to drop an existing physical slot and
+			 * recreate a logical slot with the same name. Since it is
+			 * harmless, a WARNING should be enough, no need to error-out.
+			 */
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+		}
+		else
+		{
+			SpinLockAcquire(&slot->mutex);
+
+			if (slot->data.invalidated != RS_INVAL_NONE)
+			{
+				/*
+				 * Specified physical slot have been invalidated, so no point
+				 * in waiting for it.
+				 */
+				warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+			}
+			else if (XLogRecPtrIsInvalid(slot->data.restart_lsn) ||
+					 slot->data.restart_lsn < wait_for_lsn)
+			{
+				bool	inactive = (slot->active_pid == 0);
+
+				SpinLockRelease(&slot->mutex);
+
+				/* Log warning if no active_pid for this physical slot */
+				if (inactive)
+					ereport(WARNING,
+							errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+								   name, "standby_slot_names"),
+							errdetail("Logical replication is waiting on the "
+									  "standby associated with \"%s\".", name),
+							errhint("Consider starting standby associated with "
+									"\"%s\" or amend standby_slot_names.", name));
+
+				/* Continue if the current slot hasn't caught up. */
+				continue;
+			}
+			else
+			{
+				Assert(slot->data.restart_lsn >= wait_for_lsn);
+			}
+
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving the given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	if (standby_slots == NIL)
+		return;
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			RereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		FilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		/*
+		 * We wait for the slots in the standby_slot_names to catch up, but we
+		 * use a timeout so we can also check the if the standby_slot_names has
+		 * been changed.
+		 */
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, 1000,
+									WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..e6ffb048b2 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -491,6 +501,12 @@ pg_logical_replication_slot_advance(XLogRecPtr moveto)
 											   .segment_close = wal_segment_close),
 									NULL, NULL, NULL);
 
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to moveto lsn.
+		 */
+		WaitForStandbyConfirmation(moveto);
+
 		/*
 		 * Start reading at the slot's restart_lsn, which we know to point to
 		 * a valid record.
@@ -679,6 +695,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +751,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +791,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 26ded928a7..ca61a99785 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -387,7 +387,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..5abdfd11fa 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool		failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,78 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up the logical walsender processes with failover-enabled slots if the
+ * currently acquired physical slot is specified in standby_slot_names
+ * GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, we also
+ * wait for all the specified streaming replication standby servers to
+ * confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr even when we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
-	if (RecentFlushPtr != InvalidXLogRecPtr &&
-		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	if (!XLogRecPtrIsInvalid(RecentFlushPtr) && loc <= RecentFlushPtr)
+	{
+		FilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know
+		 * we have enough WAL available and all the standby servers have
+		 * confirmed receipt of WAL up to RecentFlushPtr. This is particularly
+		 * interesting if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1670,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			RereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1685,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			FilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,9 +1724,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
+		{
+			/* Already caught up and doesn't need to wait for standby_slots. */
 			break;
+		}
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
 		WalSndCaughtUp = true;
@@ -1654,9 +1775,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +1942,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2179,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3442,7 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3512,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another
+	 * CV that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..bd710b5910 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -75,6 +75,7 @@ GSS_OPEN_SERVER	"Waiting to read data from the client while establishing a GSSAP
 LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to remote server."
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
+WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f7c9882f7c..9a54e04821 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4571,6 +4571,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..5d940b72cd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -329,6 +329,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8c0b5486b9..373c2514df 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4618,6 +4618,7 @@ getSubscriptions(Archive *fout)
 	int			i_subsynccommit;
 	int			i_subpublications;
 	int			i_suborigin;
+	int			i_subfailoverstate;
 	int			i,
 				ntups;
 
@@ -4673,14 +4674,22 @@ getSubscriptions(Archive *fout)
 		appendPQExpBufferStr(query,
 							 " s.subpasswordrequired,\n"
 							 " s.subrunasowner,\n"
-							 " s.suborigin\n");
+							 " s.suborigin,\n");
 	else
 		appendPQExpBuffer(query,
 						  " 't' AS subpasswordrequired,\n"
 						  " 't' AS subrunasowner,\n"
-						  " '%s' AS suborigin\n",
+						  " '%s' AS suborigin,\n",
 						  LOGICALREP_ORIGIN_ANY);
 
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(query,
+							 " s.subfailoverstate\n");
+	else
+		appendPQExpBuffer(query,
+						  " '%c' AS subfailoverstate\n",
+						  LOGICALREP_FAILOVER_STATE_DISABLED);
+
 	appendPQExpBufferStr(query,
 						 "FROM pg_subscription s\n"
 						 "WHERE s.subdbid = (SELECT oid FROM pg_database\n"
@@ -4709,6 +4718,7 @@ getSubscriptions(Archive *fout)
 	i_subsynccommit = PQfnumber(res, "subsynccommit");
 	i_subpublications = PQfnumber(res, "subpublications");
 	i_suborigin = PQfnumber(res, "suborigin");
+	i_subfailoverstate = PQfnumber(res, "subfailoverstate");
 
 	subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo));
 
@@ -4746,6 +4756,8 @@ getSubscriptions(Archive *fout)
 		subinfo[i].subpublications =
 			pg_strdup(PQgetvalue(res, i, i_subpublications));
 		subinfo[i].suborigin = pg_strdup(PQgetvalue(res, i, i_suborigin));
+		subinfo[i].subfailoverstate =
+			pg_strdup(PQgetvalue(res, i, i_subfailoverstate));
 
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(subinfo[i].dobj), fout);
@@ -4771,6 +4783,7 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	int			npubnames = 0;
 	int			i;
 	char		two_phase_disabled[] = {LOGICALREP_TWOPHASE_STATE_DISABLED, '\0'};
+	char		failover_disabled[] = {LOGICALREP_FAILOVER_STATE_DISABLED, '\0'};
 
 	/* Do nothing in data-only dump */
 	if (dopt->dataOnly)
@@ -4818,6 +4831,9 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	if (strcmp(subinfo->subtwophasestate, two_phase_disabled) != 0)
 		appendPQExpBufferStr(query, ", two_phase = on");
 
+	if (strcmp(subinfo->subfailoverstate, failover_disabled) != 0)
+		appendPQExpBufferStr(query, ", failover = true");
+
 	if (strcmp(subinfo->subdisableonerr, "t") == 0)
 		appendPQExpBufferStr(query, ", disable_on_error = true");
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2fe3cbed9a..f62a4dfd4b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -671,6 +671,7 @@ typedef struct _SubscriptionInfo
 	char	   *subsynccommit;
 	char	   *subpublications;
 	char	   *suborigin;
+	char	   *subfailoverstate;
 } SubscriptionInfo;
 
 /*
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..e16286f18c 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,6 +679,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
@@ -687,6 +688,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +699,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..2d8bcb26f9 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced to the
+								 * physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 020e7aa1cc..cb3a2b3aed 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -159,7 +159,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -179,8 +179,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 049801186c..905964a2e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3327,7 +3327,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 77e8b13764..bbc03bb76b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..1d987c7072 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)? Only
+	 * relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,10 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+extern void WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
+extern void FilterStandbySlots(XLogRecPtr wait_for_lsn,
+									 List **standby_slots);
+extern void RereadConfigAndReInitSlotList(List **standby_slots);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 949e874f21..f1135762fb 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -355,9 +355,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									  const char *slotname,
+									  bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -399,6 +410,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -428,8 +440,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 60313980a9..1abd813623 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -12,6 +12,8 @@
 #ifndef _WALSENDER_H
 #define _WALSENDER_H
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -45,6 +47,7 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index db73408937..84bb79ac0f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -256,7 +256,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..083b558448 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_standby_failover_slots_sync.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
new file mode 100644
index 0000000000..a30db75a16
--- /dev/null
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -0,0 +1,301 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+##################################################
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+##################################################
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create a publication on the primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres',
+	"CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init;
+$subscriber1->start;
+
+# Create a table and a subscription with failover = true
+$subscriber1->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);
+]);
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init;
+$subscriber2->start;
+$subscriber2->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	CREATE SUBSCRIPTION regress_mysub2 CONNECTION '$publisher_connstr' PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);
+]);
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with the specified physical replication slot so
+# that the logical replication slot won't receive changes until the standby
+# comes up.
+$standby1->stop;
+
+# Create some data on the primary
+my $primary_row_count = 10;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('regress_mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary until standby1 acknowledges changes"
+);
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 acknowledges changes");
+
+# Stop the standby associated with the specified physical replication slot so
+# that the logical replication slot won't receive changes until the standby
+# slot's restart_lsn is advanced or the slot is removed from the
+# standby_slot_names list.
+$publisher->safe_psql('postgres', "TRUNCATE tab_int;");
+$publisher->wait_for_catchup('regress_mysub1');
+$standby1->stop;
+
+##################################################
+# Verify that when using pg_logical_slot_get_changes to consume changes from a
+# logical slot with failover enabled, it will also wait for the slots specified
+# in standby_slot_names to catch up.
+##################################################
+
+# Create a logical 'test_decoding' replication slot with failover enabled
+$publisher->safe_psql('postgres',
+	"SELECT pg_create_logical_replication_slot('test_slot', 'test_decoding', false, false, true);"
+);
+
+my $back_q = $primary->background_psql('postgres', on_error_stop => 0);
+my $pid = $back_q->query('SELECT pg_backend_pid()');
+
+# Try and get changes from the logical slot with failover enabled.
+my $offset = -s $primary->logfile;
+$back_q->query_until(qr//,
+	"SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL);\n");
+
+# Wait until the primary server logs a warning indicating that it is waiting
+# for the sb1_slot to catch up.
+$primary->wait_for_log(
+	qr/WARNING: ( [A-Z0-9]+:)? replication slot \"sb1_slot\" specified in parameter \"standby_slot_names\" does not have active_pid/,
+	$offset);
+
+ok($primary->safe_psql('postgres', "SELECT pg_cancel_backend($pid)"),
+	"cancelling pg_logical_slot_get_changes command");
+
+$back_q->quit;
+
+$publisher->safe_psql('postgres',
+	"SELECT pg_drop_replication_slot('test_slot');"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we manually advance this slot's LSN to the
+# latest position.
+##################################################
+
+# Create some data on the primary
+$primary_row_count = 10;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# The subscription that's up and running and is enabled for failover doesn't
+# get the data from primary and keeps waiting for the standby specified in
+# standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary as long as standby1 restart_lsn has not been updated"
+);
+
+# Advance the lsn of the standby slot manually
+my $lsn = $primary->lsn('write');
+$primary->safe_psql('postgres',
+	"SELECT * FROM pg_replication_slot_advance('sb1_slot', '$lsn');"
+);
+
+# Now that the standby lsn has advanced, primary must send the decoded
+# changes to the subscription.
+$publisher->wait_for_catchup('regress_mysub1', 'replay', $lsn);
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1's restart_lsn has been updated"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we remove the slot from standby_slot_names.
+##################################################
+
+# Create some data on the primary
+$primary_row_count = 20;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(11,20);");
+
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 10 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data as the sb1_slot doesn't catch up");
+
+# Remove the standby from the standby_slot_names list and reload the
+# configuration.
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', "''");
+$primary->reload;
+
+# Since there are no slots in standby_slot_names, the primary server should now
+# send the decoded changes to the subscription.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 is removed from the standby_slot_names list"
+);
+
+# Put the standby back on the primary_slot_name for the rest of the tests
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', 'sb1_slot');
+$primary->reload;
+
+##################################################
+# Test that when a subscription with failover enabled is created, it will alter
+# the failover property of the corresponding slot on the publisher.
+##################################################
+
+# Create a slot on the publisher with failover disabled
+$primary->safe_psql('postgres',
+	"SELECT 'init' FROM pg_create_logical_replication_slot('lsub3_slot', 'pgoutput', false, false, false);"
+);
+
+# Confirm that the failover flag on the slot is turned off
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"f",
+	'logical slot has failover false on the primary');
+
+# Create another subscription (using the same slot created above) that enables
+# failover.
+$subscriber1->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub3 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub3_slot, copy_data=false, failover = true, create_slot = false);"
+);
+
+# Confirm that the failover flag on the slot has now been turned on
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"t",
+	'logical slot has failover true on the primary');
+
+$subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ba41149b88..199863c6b5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3870,6 +3871,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.30.0.windows.2



  [application/octet-stream] v51-0002-Add-logical-slot-sync-capability-to-the-physical.patch (89.2K, ../../OS0PR01MB57166472DB3D01F7407259179496A@OS0PR01MB5716.jpnprd01.prod.outlook.com/4-v51-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From d417f4480fac2861294e520cd43eb27518df8b8b Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Wed, 20 Dec 2023 20:07:24 +0800
Subject: [PATCH v51 2/3] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The logical replication slots on the primary can be synchronized to the hot
standby by enabling the failover option during slot creation and setting
'enable_syncslot' on the standby. For the synchronization to work, it is
mandatory to have a physical replication slot between the primary and the
standby, and hot_standby_feedback must be enabled on the standby.

All the failover logical replication slots on the primary (assuming
configurations are appropriate) are automatically created on the physical
standbys and are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

The nap time of the worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. If
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated.

If a logical slot on the primary is valid but is invalidated on the standby,
then that slot is dropped and recreated on the standby in next sync-cycle. It
is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently). This situation may occur due to the
following reasons:
- The max_slot_wal_keep_size on the standby is insufficient to retain WAL
  records from the restart_lsn of the slot.
- primary_slot_name is temporarily reset to null and the physical slot is
  removed.
- The primary changes wal_level to a level lower than logical.

The slots synchronization status on the standby can be monitored using
'sync_state' column of pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but slot is not ready yet for periodic syncs,
'r': ready for periodic syncs.
---
 doc/src/sgml/bgworker.sgml                    |   65 +-
 doc/src/sgml/config.sgml                      |   33 +-
 doc/src/sgml/logicaldecoding.sgml             |   31 +
 doc/src/sgml/system-views.sgml                |   35 +
 src/backend/access/transam/xlogrecovery.c     |   18 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    4 +
 src/backend/postmaster/postmaster.c           |   10 +
 .../libpqwalreceiver/libpqwalreceiver.c       |   41 +
 src/backend/replication/logical/Makefile      |    1 +
 src/backend/replication/logical/logical.c     |   25 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1315 +++++++++++++++++
 src/backend/replication/logical/worker.c      |   15 +-
 src/backend/replication/slot.c                |   34 +-
 src/backend/replication/slotfuncs.c           |   37 +-
 src/backend/replication/walsender.c           |    6 +-
 src/backend/storage/ipc/ipci.c                |    2 +
 src/backend/tcop/postgres.c                   |   11 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   10 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/postmaster/bgworker.h             |    1 +
 src/include/replication/logicalworker.h       |    1 +
 src/include/replication/slot.h                |   22 +-
 src/include/replication/walreceiver.h         |   18 +
 src/include/replication/worker_internal.h     |   11 +
 .../t/050_standby_failover_slots_sync.pl      |  145 ++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    2 +
 32 files changed, 1885 insertions(+), 33 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2c393385a9..a7cfe6c58c 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -114,18 +114,59 @@ typedef struct BackgroundWorker
 
   <para>
    <structfield>bgw_start_time</structfield> is the server state during which
-   <command>postgres</command> should start the process; it can be one of
-   <literal>BgWorkerStart_PostmasterStart</literal> (start as soon as
-   <command>postgres</command> itself has finished its own initialization; processes
-   requesting this are not eligible for database connections),
-   <literal>BgWorkerStart_ConsistentState</literal> (start as soon as a consistent state
-   has been reached in a hot standby, allowing processes to connect to
-   databases and run read-only queries), and
-   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system has
-   entered normal read-write state).  Note the last two values are equivalent
-   in a server that's not a hot standby.  Note that this setting only indicates
-   when the processes are to be started; they do not stop when a different state
-   is reached.
+   <command>postgres</command> should start the process. Note that this setting
+   only indicates when the processes are to be started; they do not stop when
+   a different state is reached. Possible values are:
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>BgWorkerStart_PostmasterStart</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_PostmasterStart</primary></indexterm>
+       Start as soon as postgres itself has finished its own initialization;
+       processes requesting this are not eligible for database connections.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState</primary></indexterm>
+       Start as soon as a consistent state has been reached in a hot-standby,
+       allowing processes to connect to databases and run read-only queries.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState_HotStandby</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState_HotStandby</primary></indexterm>
+       Same meaning as <literal>BgWorkerStart_ConsistentState</literal> but
+       it is more strict in terms of the server i.e. start the worker only
+       if it is hot-standby.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_RecoveryFinished</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_RecoveryFinished</primary></indexterm>
+       Start as soon as the system has entered normal read-write state. Note
+       that the <literal>BgWorkerStart_ConsistentState</literal> and
+      <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
+       in a server that's not a hot standby.
+      </para>
+     </listitem>
+    </varlistentry>
+
+   </variablelist>
   </para>
 
   <para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 7993fe3cdd..bbb755f057 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4373,6 +4373,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must configure
+        <literal>enable_syncslot = true</literal> so they can receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4568,8 +4574,13 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           <varname>primary_conninfo</varname> string, or in a separate
           <filename>~/.pgpass</filename> file on the standby server (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          If slot synchronization is enabled (see
+          <xref linkend="guc-enable-syncslot"/>) then it is also
+          necessary to specify <literal>dbname</literal> in the
+          <varname>primary_conninfo</varname> string. This will only be used for
+          slot synchronization. It is ignored for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4894,6 +4905,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..de6cdbe2bc 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,37 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and setting
+     <xref linkend="guc-enable-syncslot"/> on the standby. For the synchronization
+     to work, it is mandatory to have a physical replication slot between the
+     primary and the standby, and <varname>hot_standby_feedback</varname> must
+     be enabled on the standby. It's also highly recommended that the said
+     physical replication slot is named in <varname>standby_slot_names</varname>
+     list on the primary, to prevent the subscriber from consuming changes
+     faster than the hot standby.
+    </para>
+
+    <para>
+     The ability to resume logical replication after failover depends upon the
+     <link linkend="view-pg-replication-slots">pg_replication_slots</link>.<structfield>sync_state</structfield>
+     value for the synchronized slots on the standby at the time of failover.
+     Only slots that have attained "ready" sync_state ('r') on the standby
+     before failover can be used for logical replication after failover. Slots
+     that have not yet reached 'r' state (they are still 'i') will be dropped,
+     therefore logical replication for those slots cannot be resumed. For
+     example, if the synchronized slot could not become sync-ready on the
+     standby due to a disabled subscription, then the subscription cannot be
+     resumed after failover even when it is enabled.
+    </para>
+    <para>
+     If the primary is idle, then the synchronized slots on the standby may
+     take a noticeable time to reach the ready ('r') sync_state. This can
+     be sped up by calling the
+     <function>pg_log_standby_snapshot</function> function on the primary.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..9847342601 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,41 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has configured <xref linkend="guc-enable-syncslot"/> = true.
+      Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para><literal>n</literal> = none for user created slots,
+         </para>
+        </listitem>
+        <listitem>
+         <para><literal>i</literal> = sync initiated for the slot but slot
+         is not ready yet for periodic syncs,
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>r</literal> = ready for periodic syncs.
+         </para>
+        </listitem>
+       </itemizedlist>
+      </para>
+      <para>
+      The hot standby can have any of these sync_state values for the slots but
+      on a hot standby, the slots with state 'r' and 'i' can neither be used
+      for logical decoding nor dropped by the user.
+      The sync_state has no meaning on the primary server; the primary
+      sync_state value is default 'n' for all slots but may (if leftover
+      from a promoted standby)  also be 'r'.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index a2c8fa3981..444f4d23cc 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1436,23 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 *
+	 * We do not update the sync_state from READY to NONE here, as any failed
+	 * update could leave some slots in the 'NONE' state, causing issues during
+	 * slot sync after restarting the server as a standby. While updating after
+	 * switching to the new timeline is an option, it does not simplify the
+	 * handling for both READY and NONE state slots. Therefore, we retain the
+	 * READY state slots after promotion as they can provide useful information
+	 * about their origin.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 3c99cf6047..7f74f53ad1 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -21,6 +21,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "replication/worker_internal.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -129,6 +130,9 @@ static const struct
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 651b85ea74..9b74a49663 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -115,6 +115,7 @@
 #include "postmaster/syslogger.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -1003,6 +1004,12 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	/*
+	 * Register the slot sync worker here to kick start slot-sync operation
+	 * sooner on the physical standby.
+	 */
+	SlotSyncWorkerRegister();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
@@ -5740,6 +5747,9 @@ bgworker_should_start_now(BgWorkerStartTime start_time)
 		case PM_HOT_STANDBY:
 			if (start_time == BgWorkerStart_ConsistentState)
 				return true;
+			if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
+					 pmState != PM_RUN)
+				return true;
 			/* fall through */
 
 		case PM_RECOVERY:
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index cf11b98da3..f96f377d29 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -418,6 +421,44 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..c21d081346 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,31 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	if (RecoveryInProgress())
+	{
+		/*
+		 * Do not allow consumption of a "synchronized" slot until the standby
+		 * gets promoted.
+		 */
+		if (slot->data.sync_state != SYNCSLOT_STATE_NONE)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot use replication slot \"%s\" for logical "
+						"decoding", NameStr(slot->data.name)),
+					 errdetail("This slot is being synced from the primary server."),
+					 errhint("Specify another replication slot.")));
+	}
+	else
+	{
+		/*
+		 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+		 * promotion.
+		 */
+		if (slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			elog(ERROR, "replication slot \"%s\" was not synced completely "
+				"from the primary server", NameStr(slot->data.name));
+	}
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..110ee2a746
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1315 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * While creating the slot on physical standby, if the local restart_lsn and/or
+ * local catalog_xmin is ahead of those on the remote then the worker cannot
+ * create the local slot in sync with the primary server because that would
+ * mean moving the local slot backwards and the standby might not have WALs
+ * retained for old LSN. In this case, the worker will wait for the primary
+ * server slot's restart_lsn and catalog_xmin to catch up with the local one
+ * before attempting the actual sync. Meanwhile, it will persist the slot with
+ * sync_state as SYNCSLOT_STATE_INITIATED('i'). Once the primary server catches
+ * up, it will move the slot to SYNCSLOT_STATE_READY('r') state and will perform
+ * the sync periodically.
+ *
+ * The worker also takes care of dropping the slots which were created by it
+ * and are currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/table.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_database.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/*
+ * Struct for sharing information between startup process and slot
+ * sync worker.
+ *
+ * Slot sync worker's pid is needed by startup process in order to
+ * shut it down during promotion.
+ */
+typedef struct SlotSyncWorkerCtx
+{
+	pid_t		pid;
+	slock_t		mutex;
+} SlotSyncWorkerCtx;
+
+SlotSyncWorkerCtx *SlotSyncWorker = NULL;
+
+/* GUC variable */
+bool		enable_syncslot = false;
+
+/* The last sync-cycle time when the worker updated any of the slots. */
+static TimestampTz last_update_time;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn);
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WAIT_PRIMARY_CATCHUP_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * If passed, *wait_attempts_exceeded will be set to true only if this
+ * function exits due to exhausting its wait attempts. It will be false
+ * in all the other cases.
+ *
+ * Returns true if remote_slot could catch up with the locally reserved
+ * position.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *wait_attempts_exceeded)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+#define WAIT_PRIMARY_CATCHUP_ATTEMPTS 5
+
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+	Assert(wait_attempts_exceeded == NULL || *wait_attempts_exceeded == false);
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn,"
+					 " confirmed_flush_lsn, catalog_xmin"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		bool		new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *tupslot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Handle any termination request if any */
+		ProcessSlotSyncInterrupts(wrconn);
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot \"%s\" info from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
+		{
+			ereport(WARNING,
+					(errmsg("aborting initial sync for slot \"%s\"",
+							remote_slot->name),
+					 errdetail("This slot was not found on the primary server.")));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(tupslot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(tupslot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			/*
+			 * If the local-slot is in 'RS_EPHEMERAL' state, it will not be
+			 * persisted in the caller and ReplicationSlotRelease() will drop
+			 * it. But if the local slot is already persisted and has 'i'
+			 * sync_state, then it will be marked as invalidated in the caller
+			 * and next time onwards its sync will be skipped.
+			 */
+			ereport(WARNING,
+					(errmsg("aborting initial sync for slot \"%s\"",
+							remote_slot->name),
+					 errdetail("This slot was invalidated on the primary server.")));
+			pfree(cmd.data);
+			ExecClearTuple(tupslot);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * Having got a valid restart_lsn, the confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(tupslot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(tupslot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(tupslot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WAIT_PRIMARY_CATCHUP_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\"",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			if (wait_attempts_exceeded)
+				*wait_attempts_exceeded = true;
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up (refer
+ * to the comment atop the file for details on this wait)
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			local_slots = lappend(local_slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(local_slots);
+}
+
+/*
+ * Get list of local logical slots which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slots(void)
+{
+	List	   *local_slots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			local_slots = lappend(local_slots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return local_slots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+check_sync_slot_on_remote(ReplicationSlot *local_slot, List *remote_slots,
+						  bool *locally_invalidated)
+{
+	ListCell   *lc;
+
+	foreach(lc, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			SpinLockAcquire(&local_slot->mutex);
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+			SpinLockRelease(&local_slot->mutex);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Additionally, it drops slots that are valid on the primary but got
+ * invalidated on the standby. This situation may occur due to the following
+ * reasons:
+ * - The max_slot_wal_keep_size on the standby is insufficient to retain WAL
+ *   records from the restart_lsn of the slot.
+ * - primary_slot_name is temporarily reset to null and the physical slot is
+ *   removed.
+ * - The primary changes wal_level to a level lower than logical.
+ *
+ * The assumption is that these dropped local invalidated slots will get
+ * recreated in next sync-cycle and it is okay to drop and recreate such slots
+ * as long as these are not consumable on the standby (which is the case
+ * currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	local_slots = get_local_synced_slots();
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc);
+		bool		remote_exists = false;
+		bool		locally_invalidated = false;
+
+		remote_exists = check_sync_slot_on_remote(local_slot, remote_slot_list,
+												  &locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!remote_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ *
+ * Returns TRUE if the local slot is updated.
+ */
+static bool
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot)
+{
+	ReplicationSlot *slot;
+	bool		slot_updated = false;
+
+	/*
+	 * Sanity check: Make sure that concerned WAL is received before syncing
+	 * slot to target lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* Search for the named slot */
+	if ((slot = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		char		sync_state;
+
+		SpinLockAcquire(&slot->mutex);
+		sync_state = slot->data.sync_state;
+		SpinLockRelease(&slot->mutex);
+
+		/* User created slot with the same name exists, raise ERROR. */
+		if (sync_state == SYNCSLOT_STATE_NONE)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("skipping sync of slot \"%s\" as it is a user created"
+							" slot", remote_slot->name),
+					 errdetail("This slot has failover enabled on the primary and"
+							   " thus is sync candidate but user created slot with"
+							   " the same name already exists on the standby.")));
+
+		/*
+		 * Slot created by the slot sync worker exists, sync it.
+		 *
+		 * It is important to acquire the slot here before checking
+		 * invalidation. If we don't acquire the slot first, there could be a
+		 * race condition that the local slot could be invalidated just after
+		 * checking the 'invalidated' flag here and we could end up
+		 * overwriting 'invalidated' flag to remote_slot's value. See
+		 * InvalidatePossiblyObsoleteSlot() where it invalidates slot directly
+		 * if the slot is not acquired by other processes.
+		 */
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		Assert(slot == MyReplicationSlot);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (slot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&slot->mutex);
+			slot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Skip the sync of an invalidated slot */
+		if (slot->data.invalidated != RS_INVAL_NONE)
+		{
+			ReplicationSlotRelease();
+			return false;
+		}
+
+		/* Slot not ready yet, let's attempt to make it sync-ready now. */
+		if (sync_state == SYNCSLOT_STATE_INITIATED)
+		{
+			/*
+			 * Wait for the primary server to catch-up. Refer to the comment
+			 * atop the file for details on this wait.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+				TransactionIdPrecedes(remote_slot->catalog_xmin,
+									  slot->data.catalog_xmin))
+			{
+				if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+				{
+					ReplicationSlotRelease();
+					return false;
+				}
+			}
+
+			/*
+			 * Wait for primary is over, update the lsns and mark the slot as
+			 * READY for further syncs.
+			 */
+			local_slot_update(remote_slot);
+			SpinLockAcquire(&slot->mutex);
+			slot->data.sync_state = SYNCSLOT_STATE_READY;
+			SpinLockRelease(&slot->mutex);
+
+			/* Save the changes */
+			ReplicationSlotMarkDirty();
+			ReplicationSlotSave();
+			slot_updated = true;
+
+			ereport(LOG,
+					errmsg("newly locally created slot \"%s\" is sync-ready now",
+						   remote_slot->name));
+		}
+		/* Slot ready for sync, so sync it. */
+		else if (sync_state == SYNCSLOT_STATE_READY)
+		{
+			/*
+			 * Sanity check: With hot_standby_feedback enabled and
+			 * invalidations handled appropriately as above, this should never
+			 * happen.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn)
+				elog(ERROR,
+					 "not synchronizing local slot \"%s\" LSN(%X/%X)"
+					 " to remote slot's LSN(%X/%X) as synchronization "
+					 " would move it backwards", remote_slot->name,
+					 LSN_FORMAT_ARGS(slot->data.restart_lsn),
+					 LSN_FORMAT_ARGS(remote_slot->restart_lsn));
+
+			if (remote_slot->confirmed_lsn != slot->data.confirmed_flush ||
+				remote_slot->restart_lsn != slot->data.restart_lsn ||
+				remote_slot->catalog_xmin != slot->data.catalog_xmin)
+			{
+				/* Update LSN of slot to remote slot's current position */
+				local_slot_update(remote_slot);
+				ReplicationSlotSave();
+				slot_updated = true;
+			}
+		}
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+
+		/* Skip creating the local slot if remote_slot is invalidated already */
+		if (remote_slot->invalidated != RS_INVAL_NONE)
+			return false;
+
+		/* Ensure that we have transaction env needed by get_database_oid() */
+		Assert(IsTransactionState());
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover,
+							  SYNCSLOT_STATE_INITIATED);
+
+		/* For shorter lines. */
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * Wait for the primary server to catch-up. Refer to the comment atop
+		 * the file for details on this wait.
+		 */
+		if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  slot->data.catalog_xmin))
+		{
+			bool		wait_attempts_exceeded = false;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &wait_attempts_exceeded))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid i.e wait_attempts_exceeded is true) and attempt
+				 * the wait and synchronization in the next cycle.
+				 */
+				if (wait_attempts_exceeded)
+				{
+					ReplicationSlotPersist();
+					slot_updated = true;
+				}
+
+				ReplicationSlotRelease();
+				return slot_updated;
+			}
+		}
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&slot->mutex);
+		slot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&slot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		slot_updated = true;
+
+		ereport(LOG,
+				errmsg("newly locally created slot \"%s\" is sync-ready now",
+					   remote_slot->name));
+	}
+
+	ReplicationSlotRelease();
+
+	return slot_updated;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and updates
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns TRUE if any of the slots gets updated in this sync-cycle.
+ */
+static bool
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *tupslot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	ListCell   *lc;
+	bool		some_slot_updated = false;
+
+	/*
+	 * The primary_slot_name is not set yet or WALs not received yet.
+	 * Synchronization is not possible if the walreceiver is not started.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		return false;
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	StartTransactionCommand();
+
+	initStringInfo(&s);
+
+	/* Construct query to fetch slots with failover enabled. */
+	appendStringInfo(&s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover");
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(tupslot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(tupslot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(tupslot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(tupslot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(tupslot, 5,
+																	   &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(tupslot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(tupslot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(tupslot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(tupslot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(tupslot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(lc, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		some_slot_updated |= synchronize_one_slot(wrconn, remote_slot);
+	}
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	CommitTransactionCommand();
+
+	return some_slot_updated;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker",
+							&err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Checks the primary server info.
+ *
+ * Using the specified primary server connection, check whether we are a
+ * cascading standby. It also validates primary_slot_name for non-cascading
+ * standbys.
+ */
+static void
+check_primary_info(WalReceiverConn *wrconn, bool *am_cascading_standby)
+{
+#define PRIMARY_INFO_OUTPUT_COL_COUNT 2
+	WalRcvExecResult *res;
+	Oid			slotRow[PRIMARY_INFO_OUTPUT_COL_COUNT] = {BOOLOID, BOOLOID};
+	StringInfoData cmd;
+	bool		isnull;
+	TupleTableSlot *tupslot;
+	bool		valid;
+	bool		remote_in_recovery;
+	bool		tuple_ok PG_USED_FOR_ASSERTS_ONLY;
+
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	StartTransactionCommand();
+
+	Assert(am_cascading_standby != NULL);
+
+	*am_cascading_standby = false;	/* overwritten later if cascading */
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT pg_is_in_recovery(), count(*) = 1"
+					 " FROM pg_replication_slots"
+					 " WHERE slot_type='physical' AND slot_name=%s",
+					 quote_literal_cstr(PrimarySlotName));
+
+	res = walrcv_exec(wrconn, cmd.data, PRIMARY_INFO_OUTPUT_COL_COUNT, slotRow);
+	pfree(cmd.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch primary_slot_name \"%s\" info from the "
+						"primary: %s", PrimarySlotName, res->err)));
+
+	tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	tuple_ok = tuplestore_gettupleslot(res->tuplestore, true, false, tupslot);
+	Assert(tuple_ok);			/* It must return one tuple */
+
+	remote_in_recovery = DatumGetBool(slot_getattr(tupslot, 1, &isnull));
+	Assert(!isnull);
+
+	/* No need to check further, return that we are cascading standby */
+	if (remote_in_recovery)
+	{
+		*am_cascading_standby = true;
+		ExecClearTuple(tupslot);
+		walrcv_clear_result(res);
+		CommitTransactionCommand();
+		return;
+	}
+
+	valid = DatumGetBool(slot_getattr(tupslot, 2, &isnull));
+	Assert(!isnull);
+
+	if (!valid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("exiting from slot synchronization due to bad configuration"),
+		/* translator: second %s is a GUC variable name */
+				 errdetail("The primary slot \"%s\" specified by %s is not valid.",
+						   PrimarySlotName, "primary_slot_name")));
+	ExecClearTuple(tupslot);
+	walrcv_clear_result(res);
+	CommitTransactionCommand();
+}
+
+/*
+ * Check that all necessary GUCs for slot synchronization are set
+ * appropriately. If not, raise an ERROR.
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+	/* Sanity check. */
+	Assert(enable_syncslot);
+
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_slot_name"));
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be enabled.", "hot_standby_feedback"));
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("wal_level must be >= logical."));
+
+	/*
+	 * The primary_conninfo is required to make connection to primary for
+	 * getting slots information.
+	 */
+	if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_conninfo"));
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	*dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (*dbname == NULL)
+		ereport(ERROR,
+
+		/*
+		 * translator: 'dbname' is a specific option; %s is a GUC variable
+		 * name
+		 */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("'dbname' must be specified in %s.", "primary_conninfo"));
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs have changed, exit the worker and
+ * let it get restarted by the postmaster.
+ */
+static void
+slotsync_reread_config(WalReceiverConn *wrconn)
+{
+	char	   *old_primary_conninfo = pstrdup(PrimaryConnInfo);
+	char	   *old_primary_slotname = pstrdup(PrimarySlotName);
+	bool		old_hot_standby_feedback = hot_standby_feedback;
+	bool		conninfo_changed;
+	bool		primary_slotname_changed;
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	conninfo_changed = strcmp(old_primary_conninfo, PrimaryConnInfo) != 0;
+	primary_slotname_changed = strcmp(old_primary_slotname, PrimarySlotName) != 0;
+
+	if (conninfo_changed ||
+		primary_slotname_changed ||
+		(old_hot_standby_feedback != hot_standby_feedback))
+	{
+		ereport(LOG,
+				errmsg("slot sync worker will restart because of "
+					   "a parameter change"));
+		/* The exit code 1 will make postmaster restart this worker */
+		proc_exit(1);
+	}
+
+	pfree(old_primary_conninfo);
+	pfree(old_primary_slotname);
+}
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		walrcv_disconnect(wrconn);
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting down "
+					   "on receiving SIGINT"));
+		proc_exit(0);
+	}
+
+	if (ConfigReloadPending)
+		slotsync_reread_config(wrconn);
+}
+
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = InvalidPid;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+	bool		am_cascading_standby;
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	Assert(SlotSyncWorker->pid == InvalidPid);
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	validate_slotsync_parameters(&dbname);
+
+	/*
+	 * Connect to the database specified by user in primary_conninfo. We need
+	 * a database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/*
+	 * Using the specified primary server connection, check whether we are
+	 * cascading standby and validates primary_slot_name for
+	 * non-cascading-standbys.
+	 */
+	check_primary_info(wrconn, &am_cascading_standby);
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+		TimestampTz now;
+		bool		some_slot_updated;
+
+		ProcessSlotSyncInterrupts(wrconn);
+
+		if (am_cascading_standby)
+		{
+			/*
+			 * Slot synchronization is currently not supported on cascading
+			 * standby. So if we are on the cascading standby, skip the sync
+			 * and take a longer nap before we check again whether we are
+			 * still cascading standby or not.
+			 */
+			naptime = 6 * WORKER_INACTIVITY_NAPTIME_MS; /* 60 sec */
+		}
+		else
+		{
+			some_slot_updated = synchronize_slots(wrconn);
+
+			/*
+			 * If any of the slots get updated in this sync-cycle, use default
+			 * naptime and update 'last_update_time'. But if no activity is
+			 * observed in this sync-cycle, then increase naptime provided
+			 * inactivity time reaches threshold.
+			 */
+			now = GetCurrentTimestamp();
+			if (some_slot_updated)
+				last_update_time = now;
+			else if (TimestampDifferenceExceeds(last_update_time,
+												now, WORKER_INACTIVITY_THRESHOLD_MS))
+				naptime = WORKER_INACTIVITY_NAPTIME_MS;
+		}
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+
+		/*
+		 * One can promote the standby and we can no longer be a cascading
+		 * standby. So recheck here.
+		 */
+		if (am_cascading_standby)
+			check_primary_info(wrconn, &am_cascading_standby);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsLogicalSlotSyncWorker(void)
+{
+	return SlotSyncWorker->pid == MyProcPid;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (SlotSyncWorker->pid == InvalidPid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Wait for it to die. */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long. */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (SlotSyncWorker->pid == InvalidPid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Allocate and initialize slot sync worker shared memory
+ */
+void
+SlotSyncWorkerShmemInit(void)
+{
+	Size		size;
+	bool		found;
+
+	size = sizeof(SlotSyncWorkerCtx);
+	size = MAXALIGN(size);
+
+	SlotSyncWorker = (SlotSyncWorkerCtx *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
+
+	if (!found)
+	{
+		memset(SlotSyncWorker, 0, size);
+		SlotSyncWorker->pid = InvalidPid;
+		SpinLockInit(&SlotSyncWorker->mutex);
+	}
+}
+
+/*
+ * Register the background worker for slots synchronization provided
+ * enable_syncslot is ON.
+ */
+void
+SlotSyncWorkerRegister(void)
+{
+	BackgroundWorker bgw;
+
+	if (!enable_syncslot)
+	{
+		ereport(LOG,
+				(errmsg("skipping slot synchronization"),
+				 errdetail("enable_syncslot is disabled.")));
+		return;
+	}
+
+	memset(&bgw, 0, sizeof(bgw));
+
+	/* We need database connection which needs shared-memory access as well. */
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+
+	/* Start as soon as a consistent state has been reached in a hot standby */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState_HotStandby;
+
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "replication slot sync worker");
+	snprintf(bgw.bgw_type, BGW_MAXLEN,
+			 "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
+	bgw.bgw_notify_pid = 0;
+	bgw.bgw_main_arg = (Datum) 0;
+
+	RegisterBackgroundWorker(&bgw);
+}
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e46a1955e8..7b3784c212 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -141,7 +141,20 @@
  * subscribe to the new primary without losing any data.
  *
  * However, we do not enable failover for slots created by the table sync
- * worker.
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby due to the following reasons:
+ *
+ * - The standby needs to wait for the primary server to catch up because the
+ *   local restart_lsn of the newly created slot on the standby is set using
+ *   the latest redo position (GetXLogReplayRecPtr()), which is typically ahead
+ *   of the primary's restart_lsn.
+ * - The table sync slot's restart_lsn won't be advanced until the state
+ *   becomes SUBREL_STATE_CATCHUP.
+ *
+ * Therefore, if a failover happens before the restart_lsn advances, the table
+ * sync slot will not be synced to the standby. Consequently, we will not be
+ * able to subscribe to the promoted standby due to the absence of the
+ * necessary table sync slot.
  *
  * Additionally, failover is not enabled for the main slot if the table sync is
  * in progress. This is because if a failover occurs while the table sync
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 8fb0578cda..29afb959a0 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -48,6 +48,7 @@
 #include "pgstat.h"
 #include "postmaster/interrupt.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "replication/walsender_private.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
@@ -264,16 +265,22 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     user will only get commit prepared.
  * failover: If enabled, allows the slot to be synced to physical standbys so
  *     that logical replication can be resumed after failover.
+ * sync_state: Defines slot synchronization state. This function is expected
+ *     to receive either SYNCSLOT_STATE_NONE for the user created slots or
+ *     SYNCSLOT_STATE_INITIATED for the slots being synchronized on the physical
+ *     standby.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover)
+					  bool two_phase, bool failover, char sync_state)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
 
 	Assert(MyReplicationSlot == NULL);
+	Assert(sync_state == SYNCSLOT_STATE_NONE ||
+		   sync_state == SYNCSLOT_STATE_INITIATED);
 
 	ReplicationSlotValidateName(name, ERROR);
 
@@ -329,6 +336,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = sync_state;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -688,12 +696,23 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary server.")));
+
 	ReplicationSlotDropAcquired();
 }
 
@@ -713,6 +732,17 @@ ReplicationSlotAlter(const char *name, bool failover)
 				errmsg("cannot use %s with a physical replication slot",
 					   "ALTER_REPLICATION_SLOT"));
 
+	/*
+	 * Do not allow users to alter the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot alter replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary server.")));
+
 	SpinLockAcquire(&MyReplicationSlot->mutex);
 	MyReplicationSlot->data.failover = failover;
 	SpinLockRelease(&MyReplicationSlot->mutex);
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index e6ffb048b2..2ae4590957 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -44,7 +44,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false);
+						  false, SYNCSLOT_STATE_NONE);
 
 	if (immediately_reserve)
 	{
@@ -137,7 +137,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover);
+						  failover, SYNCSLOT_STATE_NONE);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 5abdfd11fa..ae5e526fcc 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1071,7 +1071,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false);
+							  false, false, SYNCSLOT_STATE_NONE);
 
 		if (reserve_wal)
 		{
@@ -1102,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover);
+							  two_phase, failover, SYNCSLOT_STATE_NONE);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, true);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0e0ac22bdd..ae2daff87b 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -37,6 +37,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -339,6 +340,7 @@ CreateOrAttachShmemStructs(void)
 	WalRcvShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7298a187d1..d87020821c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3286,6 +3286,17 @@ ProcessInterrupts(void)
 			 */
 			proc_exit(1);
 		}
+		else if (IsLogicalSlotSyncWorker())
+		{
+			elog(DEBUG1,
+				"replication slot sync worker is shutting down due to administrator command");
+
+			/*
+			 * Slot sync worker can be stopped at any time.
+			 * Use exit status 1 so the background worker is restarted.
+			 */
+			proc_exit(1);
+		}
 		else if (IsBackgroundWorker)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index bd710b5910..5b4bddf791 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 9a54e04821..7d50971fd7 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2020,6 +2021,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_POSTMASTER, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5d940b72cd..39224137e1 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -358,6 +358,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = off			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bbc03bb76b..82a11e4a31 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index e90ff376a6..8559900b70 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -79,6 +79,7 @@ typedef enum
 	BgWorkerStart_PostmasterStart,
 	BgWorkerStart_ConsistentState,
 	BgWorkerStart_RecoveryFinished,
+	BgWorkerStart_ConsistentState_HotStandby,
 } BgWorkerStartTime;
 
 #define BGW_DEFAULT_RESTART_INTERVAL	60
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index bbd71d0b42..945d2608f6 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -22,6 +22,7 @@ extern void TablesyncWorkerMain(Datum main_arg);
 
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
+extern bool IsLogicalSlotSyncWorker(void);
 
 extern void HandleParallelApplyMessageInterrupt(void);
 extern void HandleParallelApplyMessages(void);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 1d987c7072..3f2dff4845 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -52,6 +52,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +120,15 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Synchronization state for a logical slot.
+	 *
+	 * The standby can have any value among the possible values of 'i','r' and
+	 * 'n'. For primary, the default is 'n' for all slots but may also be 'r'
+	 * if leftover from a promoted standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)? Only
 	 * relevant for logical slots on the primary server.
@@ -225,9 +242,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover);
+								  bool two_phase, bool failover,
+								  char sync_state);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index f1135762fb..259d0f7065 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -279,6 +279,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -403,6 +418,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -428,6 +444,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 84bb79ac0f..9406a2666f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -237,6 +237,11 @@ extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
+/* Slot sync worker objects */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+extern PGDLLIMPORT bool enable_syncslot;
+
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
@@ -326,6 +331,12 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
+extern void SlotSyncWorkerRegister(void);
+extern void ShutDownSlotSync(void);
+extern void slotsync_drop_initiated_slots(void);
+extern void SlotSyncWorkerShmemInit(void);
+
 #define isParallelApplyWorker(worker) ((worker)->in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
 #define isTablesyncWorker(worker) ((worker)->in_use && \
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
index a30db75a16..f53c69daba 100644
--- a/src/test/recovery/t/050_standby_failover_slots_sync.pl
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -297,5 +297,150 @@ is( $primary->safe_psql(
 	'logical slot has failover true on the primary');
 
 $subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
+$primary->safe_psql('postgres', "TRUNCATE tab_int");
+
+##################################################
+# Test logical failover slots on the standby
+# Configure standby1 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub1_slot->| ----> subscriber1 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb1_slot--->| ----> standby1 (connected via streaming replication)
+#                                        |                 lsub1_slot(synced_slot)
+##################################################
+
+my $connstr_1 = $primary->connstr;
+$standby1->stop;
+$standby1->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb1_slot'
+});
+$standby1->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+my $standby1_conninfo = $standby1->connstr . ' dbname=postgres';
+
+# Wait for the standby to start sync
+$offset = -s $standby1->logfile;
+$standby1->start;
+$standby1->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();
+	 SELECT pg_log_standby_snapshot();
+	 SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby1->logfile;
+$standby1->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby and is sync ready
+is($standby1->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}),
+	"t|r",
+	'logical slot has failover as true and sync_state as ready on standby');
+
+##################################################
+# Test that a synchronized slot can not be decoded, altered and dropped by the user
+##################################################
+
+# Disable hot_standby_feedback temporarily to stop slot sync worker otherwise
+# the concerned testing scenarios here may be interrupted by different error:
+# 'ERROR:  replication slot is active for PID ..'
+
+$standby1->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = off;');
+$standby1->restart;
+
+# Dropping of synced slot should result in error
+my ($result1, $stdout, $stderr) = $standby1->psql('postgres',
+	"SELECT pg_drop_replication_slot('lsub1_slot');");
+ok($stderr =~ /ERROR:  cannot drop replication slot "lsub1_slot"/,
+	"synced slot on standby cannot be dropped");
+
+# Logical decoding on synced slot should result in error
+($result1, $stdout, $stderr) = $standby1->psql('postgres',
+	"select * from pg_logical_slot_get_changes('lsub1_slot',NULL,NULL);");
+ok($stderr =~ /ERROR:  cannot use replication slot "lsub1_slot" for logical decoding/,
+	"logical decoding is not allowed on synced slot");
+
+($result, $stdout, $stderr) = $standby1->psql(
+    'postgres',
+    qq[ALTER_REPLICATION_SLOT lsub1_slot (failover);],
+    replication => 'database');
+ok($stderr =~ /ERROR:  cannot alter replication slot "lsub1_slot"/,
+	"synced slot on standby cannot be altered");
+
+# Enable hot_standby_feedback and restart standby
+$standby1->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = on;');
+$standby1->restart;
+
+##################################################
+# Create another slot which stays in sync_state as initiated ('i')
+# because it's a manually created slot and its lsn is not advanced.
+##################################################
+
+# Create a logical slot with failover = true
+$primary->psql('postgres',
+		q{SELECT pg_create_logical_replication_slot('logical_slot','pgoutput', false, true, true);});
+
+# Wait for the standby to start sync
+$offset = -s $standby1->logfile;
+$standby1->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"logical_slot\"/,
+	$offset);
+
+# Confirm that the logical slot is created on the standby and is in sync initiated state
+  is($standby1->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'logical_slot';}),
+	"t|i",
+	'logical slot has failover as true and sync_state as initiated on standby');
+
+##################################################
+# Promote the standby1 to primary. Confirm that:
+# a) the sync-ready('r') slot 'lsub1_slot' is retained on the new primary
+# b) the initiated('i') slot 'logical_slot' is dropped on promotion
+# c) logical replication for regress_mysub1 is resumed successfully after failover
+##################################################
+$standby1->promote;
+
+# Update subscription with the new primary's connection info
+$subscriber1->safe_psql('postgres',
+	"ALTER SUBSCRIPTION regress_mysub1 DISABLE;
+	 ALTER SUBSCRIPTION regress_mysub1 CONNECTION '$standby1_conninfo';
+	 ALTER SUBSCRIPTION regress_mysub1 ENABLE; ");
+
+is($standby1->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots WHERE slot_name in ('logical_slot','lsub1_slot');}),
+	'lsub1_slot',
+	'synced slot retained on the new primary');
+
+# Insert data on the new primary
+$primary_row_count = 10;
+$standby1->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+$standby1->wait_for_catchup('regress_mysub1');
+
+# Confirm that data in tab_int replicated on subscriber
+is( $subscriber1->safe_psql('postgres', q{SELECT count(*) FROM tab_int;}),
+	"$primary_row_count",
+	'data replicated from the new primary');
 
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 199863c6b5..10c6f32a30 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2321,6 +2321,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2579,6 +2580,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorkerCtx
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.30.0.windows.2



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-18 23:20                                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-19 06:06                                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-19 11:40                                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-19 13:05                                                                     ` RE: Synchronizing slots from primary to standby Hayato Kuroda (Fujitsu) <[email protected]>
  2023-12-20 05:52                                                                       ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-20 08:03                                                                         ` RE: Synchronizing slots from primary to standby Hayato Kuroda (Fujitsu) <[email protected]>
  2023-12-20 12:42                                                                           ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-12-21 02:23                                                                             ` Zhijie Hou (Fujitsu) <[email protected]>
  1 sibling, 0 replies; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-12-21 02:23 UTC (permalink / raw)
  To: Hayato Kuroda (Fujitsu) <[email protected]>; 'Amit Kapila' <[email protected]>; shveta malik <[email protected]>; +Cc: Peter Smith <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Wednesday, December 20, 2023 8:42 PM Zhijie Hou (Fujitsu) <[email protected]> wrote:
> 
> Attach the V51 patch set which addressed Kuroda-san's comments.
> I also tried to improve the test in 0003 to make it stable.

The patches conflict with a recent commit dc21234.
Here is the rebased V51_2 version, there is no code changes in this version.

Best Regards,
Hou zj


Attachments:

  [application/octet-stream] v51_2-0003-Additional-test-to-validate-the-restart_lsn-of-s.patch (3.6K, ../../OS0PR01MB5716DAF72265388A2AD424119495A@OS0PR01MB5716.jpnprd01.prod.outlook.com/2-v51_2-0003-Additional-test-to-validate-the-restart_lsn-of-s.patch)
  download | inline diff:
From 5efafe70ce7cc299e8dcfd96e121687029b667d3 Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Tue, 19 Dec 2023 16:31:49 +0530
Subject: [PATCH v51 3/3] Additional test to validate the restart_lsn of synced
 slot

---
 .../t/050_standby_failover_slots_sync.pl      | 47 +++++++++++++++++--
 1 file changed, 42 insertions(+), 5 deletions(-)

diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
index cda99e9791..7739c8b0c6 100644
--- a/src/test/recovery/t/050_standby_failover_slots_sync.pl
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -358,6 +358,45 @@ is($standby1->safe_psql('postgres',
 	"t|r",
 	'logical slot has failover as true and sync_state as ready on standby');
 
+##################################################
+# Test to confirm that restart_lsn and confirmed_flush_lsn of the logical slot
+# on the primary is synced to the standby
+##################################################
+
+# Insert data on the primary
+$primary->safe_psql(
+	'postgres', qq[
+	TRUNCATE TABLE tab_int;
+	INSERT INTO tab_int SELECT generate_series(1, 10);
+]);
+
+$primary->wait_for_catchup('regress_mysub1');
+
+# Do not allow any further advancement of the restart_lsn and
+# confirmed_flush_lsn for the lsub1_slot.
+$subscriber1->safe_psql('postgres', "ALTER SUBSCRIPTION regress_mysub1 DISABLE");
+
+# Wait for the replication slot to become inactive on the publisher
+$primary->poll_query_until(
+	'postgres',
+	"SELECT COUNT(*) FROM pg_catalog.pg_replication_slots WHERE slot_name = 'lsub1_slot' AND active='f'",
+	1);
+
+# Get the restart_lsn for the logical slot lsub1_slot on the primary
+my $primary_restart_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Get the confirmed_flush_lsn for the logical slot lsub1_slot on the primary
+my $primary_flush_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that restart_lsn and of confirmed_flush_lsn lsub1_slot slot are synced
+# to the standby
+ok( $standby1->poll_query_until(
+		'postgres',
+		"SELECT '$primary_restart_lsn' = restart_lsn AND '$primary_flush_lsn' = confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';"),
+	'restart_lsn and confirmed_flush_lsn of slot lsub1_slot synced to standby');
+
 ##################################################
 # Test that a synchronized slot can not be decoded, altered and dropped by the user
 ##################################################
@@ -423,8 +462,7 @@ $standby1->promote;
 
 # Update subscription with the new primary's connection info
 $subscriber1->safe_psql('postgres',
-	"ALTER SUBSCRIPTION regress_mysub1 DISABLE;
-	 ALTER SUBSCRIPTION regress_mysub1 CONNECTION '$standby1_conninfo';
+	"ALTER SUBSCRIPTION regress_mysub1 CONNECTION '$standby1_conninfo';
 	 ALTER SUBSCRIPTION regress_mysub1 ENABLE; ");
 
 is($standby1->safe_psql('postgres',
@@ -433,14 +471,13 @@ is($standby1->safe_psql('postgres',
 	'synced slot retained on the new primary');
 
 # Insert data on the new primary
-$primary_row_count = 10;
 $standby1->safe_psql('postgres',
-	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+	"INSERT INTO tab_int SELECT generate_series(11, 20);");
 $standby1->wait_for_catchup('regress_mysub1');
 
 # Confirm that data in tab_int replicated on subscriber
 is( $subscriber1->safe_psql('postgres', q{SELECT count(*) FROM tab_int;}),
-	"$primary_row_count",
+	"20",
 	'data replicated from the new primary');
 
 done_testing();
-- 
2.30.0.windows.2



  [application/octet-stream] v51_2-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (147.3K, ../../OS0PR01MB5716DAF72265388A2AD424119495A@OS0PR01MB5716.jpnprd01.prod.outlook.com/3-v51_2-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From fc66b6bcc9a34dec85938ac1cb051951ab4fc622 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Thu, 21 Dec 2023 10:08:24 +0800
Subject: [PATCH v51] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to
the physical standbys so that logical replication can be resumed after
failover. It is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API.

Ex1:
CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

Ex2: (failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

A new replication command called ALTER_REPLICATION_SLOT and a
corresponding walreceiver API function named walrcv_alter_slot have been
implemented. They allow subscribers or users to modify the failover
property of a replication slot on the publisher.

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).
---
 contrib/test_decoding/expected/slot.out       |  58 +++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |  20 +-
 doc/src/sgml/ref/create_subscription.sgml     |  25 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       | 114 +++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  38 +-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  53 ++-
 src/backend/replication/logical/worker.c      |  67 +++-
 src/backend/replication/repl_gram.y           |  20 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 375 +++++++++++++++++-
 src/backend/replication/slotfuncs.c           |  25 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 178 ++++++++-
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_dump/pg_dump.c                     |  20 +-
 src/bin/pg_dump/pg_dump.h                     |   1 +
 src/bin/pg_upgrade/info.c                     |   5 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  16 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   3 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 .../t/050_standby_failover_slots_sync.pl      | 301 ++++++++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 ++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 50 files changed, 1580 insertions(+), 171 deletions(-)
 create mode 100644 src/test/recovery/t/050_standby_failover_slots_sync.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index b5624ca884..635c4d1683 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4419,6 +4419,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 9a66918171..65bf7f58a8 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER [ <replaceable class="parameter">boolean</replaceable> ]</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER [ <replaceable class="parameter">boolean</replaceable> ]</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..481e397bad 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
@@ -230,6 +233,17 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
+
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property of the new slot may differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription. When creating the slot,
+      ensure the slot failover property matches the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter value of the subscription.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..4d17e93a09 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,31 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state. It is the user's responsibility
+          to ensure that the initial table synchronization has been completed
+          before allowing the subscription to transition to the new primary.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..e68f0d401e 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,38 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If the slot_name is specified without the create_slot option,
+			 * it is possible that the user intends to use an existing slot on
+			 * the publisher, so here we alter the failover property of the
+			 * slot to match the failover value in subscription.
+			 *
+			 * We do not need to change the failover to false if the server
+			 * does not support failover (e.g. pre-PG17)
+			 */
+			else if (opts.slot_name &&
+					 (failover_enabled || walrcv_server_version(wrconn) >= 170000))
+			{
+				bool		failover_delayed = (!failover_enabled && opts.failover);
+
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+								opts.slot_name, failover_enabled ? "true" : "false"),
+						 failover_delayed ?
+						 errdetail("The failover state will be set to true once table synchronization has been completed.")
+						 : 0));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,13 +1335,22 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
@@ -1334,13 +1399,26 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 						/* translator: %s is an SQL ALTER command */
 								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
 										 isadd ?
@@ -1389,7 +1467,19 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 					ereport(ERROR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "two_phase"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "failover"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 693b3669ba..cf11b98da3 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -888,8 +892,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -918,7 +922,8 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+			appendStringInfoString(&cmd, "FAILOVER, ");
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -987,6 +992,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..330a55d35d 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 4d056c16c8..7b6170fe55 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -624,15 +624,28 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "two_phase")));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "failover")));
+
 				should_exit = true;
 			}
 		}
@@ -1430,7 +1443,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1732,10 +1746,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1743,9 +1759,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1759,9 +1774,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..e46a1955e8 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,6 +132,33 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * failover = true when creating the subscription. Enabling failover allows us
+ * to smoothly transition to the promoted standby, ensuring that we can
+ * subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
@@ -3947,6 +3974,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4510,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4568,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4608,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index a5d118ed68..fac73f402e 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -80,8 +81,9 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
-				read_replication_slot timeline_history show upload_manifest
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system read_replication_slot
+				timeline_history show upload_manifest
 %type <list>	generic_option_list
 %type <defelt>	generic_option
 %type <uintval>	opt_timeline
@@ -112,6 +114,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -259,6 +262,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -410,6 +425,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 4805da08ee..e4a155c7c8 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -302,6 +303,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_UPLOAD_MANIFEST:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..8fb0578cda 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -46,12 +46,17 @@
 #include "common/string.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "postmaster/interrupt.h"
 #include "replication/slot.h"
+#include "replication/walsender_private.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +95,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +103,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -248,10 +262,13 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     during getting changes, if the two_phase option is enabled it can skip
  *     prepare because by that time start decoding point has been moved. So the
  *     user will only get commit prepared.
+ * failover: If enabled, allows the slot to be synced to physical standbys so
+ *     that logical replication can be resumed after failover.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +328,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +697,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2202,329 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+	bool		ok;
+
+	/* Need a modifiable copy of string. */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into a list of identifiers. */
+	ok = SplitIdentifierString(rawname, ',', &elemlist);
+
+	if (!ok)
+		GUC_check_errdetail("List syntax is invalid.");
+
+	/*
+	 * If there is a syntax error in the name or if the replication slots'
+	 * data is not initialized yet (i.e., we are in the startup process), skip
+	 * the slot verification.
+	 */
+	if (!ok || !ReplicationSlotCtl)
+	{
+		pfree(rawname);
+		list_free(elemlist);
+		return ok;
+	}
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			GUC_check_errdetail("replication slot \"%s\" does not exist",
+								name);
+			ok = false;
+			break;
+		}
+
+		if (!SlotIsPhysical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			ok = false;
+			break;
+		}
+	}
+
+	pfree(rawname);
+	list_free(elemlist);
+	return ok;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	/*
+	 * Since we do not support syncing slots to cascading standbys, we return
+	 * NIL here if we are running in a standby to indicate that no standby
+	 * slots need to be waited for.
+	 */
+	if (RecoveryInProgress())
+		return NIL;
+
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+void
+RereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names;
+
+	/*
+	 * If we are running on a standby, there is no need to reload
+	 * standby_slot_names since we do not support syncing slots to cascading
+	 * standbys.
+	 */
+	if (RecoveryInProgress())
+	{
+		ProcessConfigFile(PGC_SIGHUP);
+		return;
+	}
+
+	pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn. Additionally,
+ * it removes slots that have been invalidated, dropped, or converted to
+ * logical slots.
+ */
+void
+FilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			/*
+			 * It may happen that the slot specified in standby_slot_names GUC
+			 * value is dropped, so let's skip over it.
+			 */
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+		}
+		else if (SlotIsLogical(slot))
+		{
+			/*
+			 * If a logical slot name is provided in standby_slot_names, issue
+			 * a WARNING and skip it. Although logical slots are disallowed in
+			 * the GUC check_hook(validate_standby_slots), it is still
+			 * possible for a user to drop an existing physical slot and
+			 * recreate a logical slot with the same name. Since it is
+			 * harmless, a WARNING should be enough, no need to error-out.
+			 */
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+		}
+		else
+		{
+			SpinLockAcquire(&slot->mutex);
+
+			if (slot->data.invalidated != RS_INVAL_NONE)
+			{
+				/*
+				 * Specified physical slot have been invalidated, so no point
+				 * in waiting for it.
+				 */
+				warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+			}
+			else if (XLogRecPtrIsInvalid(slot->data.restart_lsn) ||
+					 slot->data.restart_lsn < wait_for_lsn)
+			{
+				bool	inactive = (slot->active_pid == 0);
+
+				SpinLockRelease(&slot->mutex);
+
+				/* Log warning if no active_pid for this physical slot */
+				if (inactive)
+					ereport(WARNING,
+							errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+								   name, "standby_slot_names"),
+							errdetail("Logical replication is waiting on the "
+									  "standby associated with \"%s\".", name),
+							errhint("Consider starting standby associated with "
+									"\"%s\" or amend standby_slot_names.", name));
+
+				/* Continue if the current slot hasn't caught up. */
+				continue;
+			}
+			else
+			{
+				Assert(slot->data.restart_lsn >= wait_for_lsn);
+			}
+
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving the given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	if (standby_slots == NIL)
+		return;
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			RereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		FilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		/*
+		 * We wait for the slots in the standby_slot_names to catch up, but we
+		 * use a timeout so we can also check the if the standby_slot_names has
+		 * been changed.
+		 */
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, 1000,
+									WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..e6ffb048b2 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -491,6 +501,12 @@ pg_logical_replication_slot_advance(XLogRecPtr moveto)
 											   .segment_close = wal_segment_close),
 									NULL, NULL, NULL);
 
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to moveto lsn.
+		 */
+		WaitForStandbyConfirmation(moveto);
+
 		/*
 		 * Start reading at the slot's restart_lsn, which we know to point to
 		 * a valid record.
@@ -679,6 +695,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +751,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +791,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 26ded928a7..ca61a99785 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -387,7 +387,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index dbcda32554..3c5ce4641b 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1126,12 +1126,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1181,6 +1182,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1197,6 +1207,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1206,13 +1217,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1243,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1398,6 +1409,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool		failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1679,27 +1730,78 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up the logical walsender processes with failover-enabled slots if the
+ * currently acquired physical slot is specified in standby_slot_names
+ * GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, we also
+ * wait for all the specified streaming replication standby servers to
+ * confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr even when we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
-	if (RecentFlushPtr != InvalidXLogRecPtr &&
-		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	if (!XLogRecPtrIsInvalid(RecentFlushPtr) && loc <= RecentFlushPtr)
+	{
+		FilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know
+		 * we have enough WAL available and all the standby servers have
+		 * confirmed receipt of WAL up to RecentFlushPtr. This is particularly
+		 * interesting if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1720,7 +1822,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			RereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1735,8 +1837,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			FilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1764,9 +1876,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
+		{
+			/* Already caught up and doesn't need to wait for standby_slots. */
 			break;
+		}
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
 		WalSndCaughtUp = true;
@@ -1806,9 +1927,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1971,6 +2094,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2209,6 +2339,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3471,6 +3602,7 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3540,8 +3672,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another
+	 * CV that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7e79163466..20ca7669a3 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ GSS_OPEN_SERVER	"Waiting to read data from the client while establishing a GSSAP
 LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to remote server."
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
+WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 9f59440526..df5b347f4b 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4597,6 +4597,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index b2809c711a..22a7d1093e 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -334,6 +334,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8c0b5486b9..373c2514df 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4618,6 +4618,7 @@ getSubscriptions(Archive *fout)
 	int			i_subsynccommit;
 	int			i_subpublications;
 	int			i_suborigin;
+	int			i_subfailoverstate;
 	int			i,
 				ntups;
 
@@ -4673,14 +4674,22 @@ getSubscriptions(Archive *fout)
 		appendPQExpBufferStr(query,
 							 " s.subpasswordrequired,\n"
 							 " s.subrunasowner,\n"
-							 " s.suborigin\n");
+							 " s.suborigin,\n");
 	else
 		appendPQExpBuffer(query,
 						  " 't' AS subpasswordrequired,\n"
 						  " 't' AS subrunasowner,\n"
-						  " '%s' AS suborigin\n",
+						  " '%s' AS suborigin,\n",
 						  LOGICALREP_ORIGIN_ANY);
 
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(query,
+							 " s.subfailoverstate\n");
+	else
+		appendPQExpBuffer(query,
+						  " '%c' AS subfailoverstate\n",
+						  LOGICALREP_FAILOVER_STATE_DISABLED);
+
 	appendPQExpBufferStr(query,
 						 "FROM pg_subscription s\n"
 						 "WHERE s.subdbid = (SELECT oid FROM pg_database\n"
@@ -4709,6 +4718,7 @@ getSubscriptions(Archive *fout)
 	i_subsynccommit = PQfnumber(res, "subsynccommit");
 	i_subpublications = PQfnumber(res, "subpublications");
 	i_suborigin = PQfnumber(res, "suborigin");
+	i_subfailoverstate = PQfnumber(res, "subfailoverstate");
 
 	subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo));
 
@@ -4746,6 +4756,8 @@ getSubscriptions(Archive *fout)
 		subinfo[i].subpublications =
 			pg_strdup(PQgetvalue(res, i, i_subpublications));
 		subinfo[i].suborigin = pg_strdup(PQgetvalue(res, i, i_suborigin));
+		subinfo[i].subfailoverstate =
+			pg_strdup(PQgetvalue(res, i, i_subfailoverstate));
 
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(subinfo[i].dobj), fout);
@@ -4771,6 +4783,7 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	int			npubnames = 0;
 	int			i;
 	char		two_phase_disabled[] = {LOGICALREP_TWOPHASE_STATE_DISABLED, '\0'};
+	char		failover_disabled[] = {LOGICALREP_FAILOVER_STATE_DISABLED, '\0'};
 
 	/* Do nothing in data-only dump */
 	if (dopt->dataOnly)
@@ -4818,6 +4831,9 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	if (strcmp(subinfo->subtwophasestate, two_phase_disabled) != 0)
 		appendPQExpBufferStr(query, ", two_phase = on");
 
+	if (strcmp(subinfo->subfailoverstate, failover_disabled) != 0)
+		appendPQExpBufferStr(query, ", failover = true");
+
 	if (strcmp(subinfo->subdisableonerr, "t") == 0)
 		appendPQExpBufferStr(query, ", disable_on_error = true");
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2fe3cbed9a..f62a4dfd4b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -671,6 +671,7 @@ typedef struct _SubscriptionInfo
 	char	   *subsynccommit;
 	char	   *subpublications;
 	char	   *suborigin;
+	char	   *subfailoverstate;
 } SubscriptionInfo;
 
 /*
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..e16286f18c 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,6 +679,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
@@ -687,6 +688,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +699,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..2d8bcb26f9 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced to the
+								 * physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 020e7aa1cc..cb3a2b3aed 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -159,7 +159,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -179,8 +179,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 049801186c..905964a2e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3327,7 +3327,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 916c8ec8d0..5e683fdde7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index c98961c329..d9be317662 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..1d987c7072 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)? Only
+	 * relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,10 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+extern void WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
+extern void FilterStandbySlots(XLogRecPtr wait_for_lsn,
+									 List **standby_slots);
+extern void RereadConfigAndReInitSlotList(List **standby_slots);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 949e874f21..f1135762fb 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -355,9 +355,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									  const char *slotname,
+									  bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -399,6 +410,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -428,8 +440,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 60313980a9..1abd813623 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -12,6 +12,8 @@
 #ifndef _WALSENDER_H
 #define _WALSENDER_H
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -45,6 +47,7 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index db73408937..84bb79ac0f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -256,7 +256,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..083b558448 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_standby_failover_slots_sync.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
new file mode 100644
index 0000000000..81a47c7af8
--- /dev/null
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -0,0 +1,301 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+##################################################
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+##################################################
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create a publication on the primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres',
+	"CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init;
+$subscriber1->start;
+
+# Create a table and a subscription with failover = true
+$subscriber1->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);
+]);
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init;
+$subscriber2->start;
+$subscriber2->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	CREATE SUBSCRIPTION regress_mysub2 CONNECTION '$publisher_connstr' PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);
+]);
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with the specified physical replication slot so
+# that the logical replication slot won't receive changes until the standby
+# comes up.
+$standby1->stop;
+
+# Create some data on the primary
+my $primary_row_count = 10;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('regress_mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary until standby1 acknowledges changes"
+);
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 acknowledges changes");
+
+# Stop the standby associated with the specified physical replication slot so
+# that the logical replication slot won't receive changes until the standby
+# slot's restart_lsn is advanced or the slot is removed from the
+# standby_slot_names list.
+$publisher->safe_psql('postgres', "TRUNCATE tab_int;");
+$publisher->wait_for_catchup('regress_mysub1');
+$standby1->stop;
+
+##################################################
+# Verify that when using pg_logical_slot_get_changes to consume changes from a
+# logical slot with failover enabled, it will also wait for the slots specified
+# in standby_slot_names to catch up.
+##################################################
+
+# Create a logical 'test_decoding' replication slot with failover enabled
+$publisher->safe_psql('postgres',
+	"SELECT pg_create_logical_replication_slot('test_slot', 'test_decoding', false, false, true);"
+);
+
+my $back_q = $primary->background_psql('postgres', on_error_stop => 0);
+my $pid = $back_q->query('SELECT pg_backend_pid()');
+
+# Try and get changes from the logical slot with failover enabled.
+my $offset = -s $primary->logfile;
+$back_q->query_until(qr//,
+	"SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL);\n");
+
+# Wait until the primary server logs a warning indicating that it is waiting
+# for the sb1_slot to catch up.
+$primary->wait_for_log(
+	qr/WARNING: ( [A-Z0-9]+:)? replication slot \"sb1_slot\" specified in parameter \"standby_slot_names\" does not have active_pid/,
+	$offset);
+
+ok($primary->safe_psql('postgres', "SELECT pg_cancel_backend($pid)"),
+	"cancelling pg_logical_slot_get_changes command");
+
+$back_q->quit;
+
+$publisher->safe_psql('postgres',
+	"SELECT pg_drop_replication_slot('test_slot');"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we manually advance this slot's LSN to the
+# latest position.
+##################################################
+
+# Create some data on the primary
+$primary_row_count = 10;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# The subscription that's up and running and is enabled for failover doesn't
+# get the data from primary and keeps waiting for the standby specified in
+# standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary as long as standby1 restart_lsn has not been updated"
+);
+
+# Advance the lsn of the standby slot manually
+my $lsn = $primary->lsn('write');
+$primary->safe_psql('postgres',
+	"SELECT * FROM pg_replication_slot_advance('sb1_slot', '$lsn');"
+);
+
+# Now that the standby lsn has advanced, primary must send the decoded
+# changes to the subscription.
+$publisher->wait_for_catchup('regress_mysub1', 'replay', $lsn);
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1's restart_lsn has been updated"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we remove the slot from standby_slot_names.
+##################################################
+
+# Create some data on the primary
+$primary_row_count = 20;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(11,20);");
+
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 10 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data as the sb1_slot doesn't catch up");
+
+# Remove the standby from the standby_slot_names list and reload the
+# configuration.
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', "''");
+$primary->reload;
+
+# Since there are no slots in standby_slot_names, the primary server should now
+# send the decoded changes to the subscription.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 is removed from the standby_slot_names list"
+);
+
+# Put the standby back on the primary_slot_name for the rest of the tests
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', 'sb1_slot');
+$primary->reload;
+
+##################################################
+# Test that when a subscription with failover enabled is created, it will alter
+# the failover property of the corresponding slot on the publisher.
+##################################################
+
+# Create a slot on the publisher with failover disabled
+$primary->safe_psql('postgres',
+	"SELECT 'init' FROM pg_create_logical_replication_slot('lsub3_slot', 'pgoutput', false, false, false);"
+);
+
+# Confirm that the failover flag on the slot is turned off
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"f",
+	'logical slot has failover false on the primary');
+
+# Create another subscription (using the same slot created above) that enables
+# failover.
+$subscriber1->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub3 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub3_slot, copy_data=false, failover = true, create_slot = false);"
+);
+
+# Confirm that the failover flag on the slot has now been turned on
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"t",
+	'logical slot has failover true on the primary');
+
+$subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e37ef9aa76..611deeaae5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3870,6 +3871,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.30.0.windows.2



  [application/octet-stream] v51_2-0002-Add-logical-slot-sync-capability-to-the-physical.patch (89.2K, ../../OS0PR01MB5716DAF72265388A2AD424119495A@OS0PR01MB5716.jpnprd01.prod.outlook.com/4-v51_2-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From 7901ccdb707f429ff9aad6da7e943d0f459a15ab Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Thu, 21 Dec 2023 10:11:23 +0800
Subject: [PATCH v51 2/3] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The logical replication slots on the primary can be synchronized to the hot
standby by enabling the failover option during slot creation and setting
'enable_syncslot' on the standby. For the synchronization to work, it is
mandatory to have a physical replication slot between the primary and the
standby, and hot_standby_feedback must be enabled on the standby.

All the failover logical replication slots on the primary (assuming
configurations are appropriate) are automatically created on the physical
standbys and are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

The nap time of the worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. If
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated.

If a logical slot on the primary is valid but is invalidated on the standby,
then that slot is dropped and recreated on the standby in next sync-cycle. It
is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently). This situation may occur due to the
following reasons:
- The max_slot_wal_keep_size on the standby is insufficient to retain WAL
  records from the restart_lsn of the slot.
- primary_slot_name is temporarily reset to null and the physical slot is
  removed.
- The primary changes wal_level to a level lower than logical.

The slots synchronization status on the standby can be monitored using
'sync_state' column of pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but slot is not ready yet for periodic syncs,
'r': ready for periodic syncs.
---
 doc/src/sgml/bgworker.sgml                    |   65 +-
 doc/src/sgml/config.sgml                      |   33 +-
 doc/src/sgml/logicaldecoding.sgml             |   31 +
 doc/src/sgml/system-views.sgml                |   35 +
 src/backend/access/transam/xlogrecovery.c     |   18 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    4 +
 src/backend/postmaster/postmaster.c           |   10 +
 .../libpqwalreceiver/libpqwalreceiver.c       |   41 +
 src/backend/replication/logical/Makefile      |    1 +
 src/backend/replication/logical/logical.c     |   25 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1315 +++++++++++++++++
 src/backend/replication/logical/worker.c      |   15 +-
 src/backend/replication/slot.c                |   34 +-
 src/backend/replication/slotfuncs.c           |   37 +-
 src/backend/replication/walsender.c           |    6 +-
 src/backend/storage/ipc/ipci.c                |    2 +
 src/backend/tcop/postgres.c                   |   11 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   10 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/postmaster/bgworker.h             |    1 +
 src/include/replication/logicalworker.h       |    1 +
 src/include/replication/slot.h                |   22 +-
 src/include/replication/walreceiver.h         |   18 +
 src/include/replication/worker_internal.h     |   11 +
 .../t/050_standby_failover_slots_sync.pl      |  145 ++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    2 +
 32 files changed, 1885 insertions(+), 33 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2c393385a9..a7cfe6c58c 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -114,18 +114,59 @@ typedef struct BackgroundWorker
 
   <para>
    <structfield>bgw_start_time</structfield> is the server state during which
-   <command>postgres</command> should start the process; it can be one of
-   <literal>BgWorkerStart_PostmasterStart</literal> (start as soon as
-   <command>postgres</command> itself has finished its own initialization; processes
-   requesting this are not eligible for database connections),
-   <literal>BgWorkerStart_ConsistentState</literal> (start as soon as a consistent state
-   has been reached in a hot standby, allowing processes to connect to
-   databases and run read-only queries), and
-   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system has
-   entered normal read-write state).  Note the last two values are equivalent
-   in a server that's not a hot standby.  Note that this setting only indicates
-   when the processes are to be started; they do not stop when a different state
-   is reached.
+   <command>postgres</command> should start the process. Note that this setting
+   only indicates when the processes are to be started; they do not stop when
+   a different state is reached. Possible values are:
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>BgWorkerStart_PostmasterStart</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_PostmasterStart</primary></indexterm>
+       Start as soon as postgres itself has finished its own initialization;
+       processes requesting this are not eligible for database connections.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState</primary></indexterm>
+       Start as soon as a consistent state has been reached in a hot-standby,
+       allowing processes to connect to databases and run read-only queries.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState_HotStandby</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState_HotStandby</primary></indexterm>
+       Same meaning as <literal>BgWorkerStart_ConsistentState</literal> but
+       it is more strict in terms of the server i.e. start the worker only
+       if it is hot-standby.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_RecoveryFinished</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_RecoveryFinished</primary></indexterm>
+       Start as soon as the system has entered normal read-write state. Note
+       that the <literal>BgWorkerStart_ConsistentState</literal> and
+      <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
+       in a server that's not a hot standby.
+      </para>
+     </listitem>
+    </varlistentry>
+
+   </variablelist>
   </para>
 
   <para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 635c4d1683..2209eff684 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4432,6 +4432,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must configure
+        <literal>enable_syncslot = true</literal> so they can receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4627,8 +4633,13 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           <varname>primary_conninfo</varname> string, or in a separate
           <filename>~/.pgpass</filename> file on the standby server (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          If slot synchronization is enabled (see
+          <xref linkend="guc-enable-syncslot"/>) then it is also
+          necessary to specify <literal>dbname</literal> in the
+          <varname>primary_conninfo</varname> string. This will only be used for
+          slot synchronization. It is ignored for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4953,6 +4964,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..de6cdbe2bc 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,37 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and setting
+     <xref linkend="guc-enable-syncslot"/> on the standby. For the synchronization
+     to work, it is mandatory to have a physical replication slot between the
+     primary and the standby, and <varname>hot_standby_feedback</varname> must
+     be enabled on the standby. It's also highly recommended that the said
+     physical replication slot is named in <varname>standby_slot_names</varname>
+     list on the primary, to prevent the subscriber from consuming changes
+     faster than the hot standby.
+    </para>
+
+    <para>
+     The ability to resume logical replication after failover depends upon the
+     <link linkend="view-pg-replication-slots">pg_replication_slots</link>.<structfield>sync_state</structfield>
+     value for the synchronized slots on the standby at the time of failover.
+     Only slots that have attained "ready" sync_state ('r') on the standby
+     before failover can be used for logical replication after failover. Slots
+     that have not yet reached 'r' state (they are still 'i') will be dropped,
+     therefore logical replication for those slots cannot be resumed. For
+     example, if the synchronized slot could not become sync-ready on the
+     standby due to a disabled subscription, then the subscription cannot be
+     resumed after failover even when it is enabled.
+    </para>
+    <para>
+     If the primary is idle, then the synchronized slots on the standby may
+     take a noticeable time to reach the ready ('r') sync_state. This can
+     be sped up by calling the
+     <function>pg_log_standby_snapshot</function> function on the primary.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..9847342601 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,41 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has configured <xref linkend="guc-enable-syncslot"/> = true.
+      Possible values are:
+       <itemizedlist>
+        <listitem>
+         <para><literal>n</literal> = none for user created slots,
+         </para>
+        </listitem>
+        <listitem>
+         <para><literal>i</literal> = sync initiated for the slot but slot
+         is not ready yet for periodic syncs,
+         </para>
+        </listitem>
+        <listitem>
+         <para>
+          <literal>r</literal> = ready for periodic syncs.
+         </para>
+        </listitem>
+       </itemizedlist>
+      </para>
+      <para>
+      The hot standby can have any of these sync_state values for the slots but
+      on a hot standby, the slots with state 'r' and 'i' can neither be used
+      for logical decoding nor dropped by the user.
+      The sync_state has no meaning on the primary server; the primary
+      sync_state value is default 'n' for all slots but may (if leftover
+      from a promoted standby)  also be 'r'.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 6f4f81f992..aff66ccbe6 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1441,6 +1442,23 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 *
+	 * We do not update the sync_state from READY to NONE here, as any failed
+	 * update could leave some slots in the 'NONE' state, causing issues during
+	 * slot sync after restarting the server as a standby. While updating after
+	 * switching to the new timeline is an option, it does not simplify the
+	 * handling for both READY and NONE state slots. Therefore, we retain the
+	 * READY state slots after promotion as they can provide useful information
+	 * about their origin.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 3c99cf6047..7f74f53ad1 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -21,6 +21,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "replication/worker_internal.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -129,6 +130,9 @@ static const struct
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index b163e89cbb..3ccdefa9d7 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -116,6 +116,7 @@
 #include "postmaster/walsummarizer.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -1010,6 +1011,12 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	/*
+	 * Register the slot sync worker here to kick start slot-sync operation
+	 * sooner on the physical standby.
+	 */
+	SlotSyncWorkerRegister();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
@@ -5796,6 +5803,9 @@ bgworker_should_start_now(BgWorkerStartTime start_time)
 		case PM_HOT_STANDBY:
 			if (start_time == BgWorkerStart_ConsistentState)
 				return true;
+			if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
+					 pmState != PM_RUN)
+				return true;
 			/* fall through */
 
 		case PM_RECOVERY:
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index cf11b98da3..f96f377d29 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -418,6 +421,44 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..c21d081346 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,31 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	if (RecoveryInProgress())
+	{
+		/*
+		 * Do not allow consumption of a "synchronized" slot until the standby
+		 * gets promoted.
+		 */
+		if (slot->data.sync_state != SYNCSLOT_STATE_NONE)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot use replication slot \"%s\" for logical "
+						"decoding", NameStr(slot->data.name)),
+					 errdetail("This slot is being synced from the primary server."),
+					 errhint("Specify another replication slot.")));
+	}
+	else
+	{
+		/*
+		 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+		 * promotion.
+		 */
+		if (slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			elog(ERROR, "replication slot \"%s\" was not synced completely "
+				"from the primary server", NameStr(slot->data.name));
+	}
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..110ee2a746
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1315 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * While creating the slot on physical standby, if the local restart_lsn and/or
+ * local catalog_xmin is ahead of those on the remote then the worker cannot
+ * create the local slot in sync with the primary server because that would
+ * mean moving the local slot backwards and the standby might not have WALs
+ * retained for old LSN. In this case, the worker will wait for the primary
+ * server slot's restart_lsn and catalog_xmin to catch up with the local one
+ * before attempting the actual sync. Meanwhile, it will persist the slot with
+ * sync_state as SYNCSLOT_STATE_INITIATED('i'). Once the primary server catches
+ * up, it will move the slot to SYNCSLOT_STATE_READY('r') state and will perform
+ * the sync periodically.
+ *
+ * The worker also takes care of dropping the slots which were created by it
+ * and are currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/table.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_database.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/*
+ * Struct for sharing information between startup process and slot
+ * sync worker.
+ *
+ * Slot sync worker's pid is needed by startup process in order to
+ * shut it down during promotion.
+ */
+typedef struct SlotSyncWorkerCtx
+{
+	pid_t		pid;
+	slock_t		mutex;
+} SlotSyncWorkerCtx;
+
+SlotSyncWorkerCtx *SlotSyncWorker = NULL;
+
+/* GUC variable */
+bool		enable_syncslot = false;
+
+/* The last sync-cycle time when the worker updated any of the slots. */
+static TimestampTz last_update_time;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn);
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WAIT_PRIMARY_CATCHUP_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * If passed, *wait_attempts_exceeded will be set to true only if this
+ * function exits due to exhausting its wait attempts. It will be false
+ * in all the other cases.
+ *
+ * Returns true if remote_slot could catch up with the locally reserved
+ * position.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *wait_attempts_exceeded)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+#define WAIT_PRIMARY_CATCHUP_ATTEMPTS 5
+
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+	Assert(wait_attempts_exceeded == NULL || *wait_attempts_exceeded == false);
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn,"
+					 " confirmed_flush_lsn, catalog_xmin"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		bool		new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *tupslot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Handle any termination request if any */
+		ProcessSlotSyncInterrupts(wrconn);
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot \"%s\" info from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
+		{
+			ereport(WARNING,
+					(errmsg("aborting initial sync for slot \"%s\"",
+							remote_slot->name),
+					 errdetail("This slot was not found on the primary server.")));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(tupslot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(tupslot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			/*
+			 * If the local-slot is in 'RS_EPHEMERAL' state, it will not be
+			 * persisted in the caller and ReplicationSlotRelease() will drop
+			 * it. But if the local slot is already persisted and has 'i'
+			 * sync_state, then it will be marked as invalidated in the caller
+			 * and next time onwards its sync will be skipped.
+			 */
+			ereport(WARNING,
+					(errmsg("aborting initial sync for slot \"%s\"",
+							remote_slot->name),
+					 errdetail("This slot was invalidated on the primary server.")));
+			pfree(cmd.data);
+			ExecClearTuple(tupslot);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * Having got a valid restart_lsn, the confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(tupslot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(tupslot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(tupslot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WAIT_PRIMARY_CATCHUP_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\"",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			if (wait_attempts_exceeded)
+				*wait_attempts_exceeded = true;
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up (refer
+ * to the comment atop the file for details on this wait)
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			local_slots = lappend(local_slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(local_slots);
+}
+
+/*
+ * Get list of local logical slots which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slots(void)
+{
+	List	   *local_slots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			local_slots = lappend(local_slots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return local_slots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+check_sync_slot_on_remote(ReplicationSlot *local_slot, List *remote_slots,
+						  bool *locally_invalidated)
+{
+	ListCell   *lc;
+
+	foreach(lc, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			SpinLockAcquire(&local_slot->mutex);
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+			SpinLockRelease(&local_slot->mutex);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Additionally, it drops slots that are valid on the primary but got
+ * invalidated on the standby. This situation may occur due to the following
+ * reasons:
+ * - The max_slot_wal_keep_size on the standby is insufficient to retain WAL
+ *   records from the restart_lsn of the slot.
+ * - primary_slot_name is temporarily reset to null and the physical slot is
+ *   removed.
+ * - The primary changes wal_level to a level lower than logical.
+ *
+ * The assumption is that these dropped local invalidated slots will get
+ * recreated in next sync-cycle and it is okay to drop and recreate such slots
+ * as long as these are not consumable on the standby (which is the case
+ * currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	local_slots = get_local_synced_slots();
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc);
+		bool		remote_exists = false;
+		bool		locally_invalidated = false;
+
+		remote_exists = check_sync_slot_on_remote(local_slot, remote_slot_list,
+												  &locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!remote_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ *
+ * Returns TRUE if the local slot is updated.
+ */
+static bool
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot)
+{
+	ReplicationSlot *slot;
+	bool		slot_updated = false;
+
+	/*
+	 * Sanity check: Make sure that concerned WAL is received before syncing
+	 * slot to target lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* Search for the named slot */
+	if ((slot = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		char		sync_state;
+
+		SpinLockAcquire(&slot->mutex);
+		sync_state = slot->data.sync_state;
+		SpinLockRelease(&slot->mutex);
+
+		/* User created slot with the same name exists, raise ERROR. */
+		if (sync_state == SYNCSLOT_STATE_NONE)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("skipping sync of slot \"%s\" as it is a user created"
+							" slot", remote_slot->name),
+					 errdetail("This slot has failover enabled on the primary and"
+							   " thus is sync candidate but user created slot with"
+							   " the same name already exists on the standby.")));
+
+		/*
+		 * Slot created by the slot sync worker exists, sync it.
+		 *
+		 * It is important to acquire the slot here before checking
+		 * invalidation. If we don't acquire the slot first, there could be a
+		 * race condition that the local slot could be invalidated just after
+		 * checking the 'invalidated' flag here and we could end up
+		 * overwriting 'invalidated' flag to remote_slot's value. See
+		 * InvalidatePossiblyObsoleteSlot() where it invalidates slot directly
+		 * if the slot is not acquired by other processes.
+		 */
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		Assert(slot == MyReplicationSlot);
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (slot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&slot->mutex);
+			slot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Skip the sync of an invalidated slot */
+		if (slot->data.invalidated != RS_INVAL_NONE)
+		{
+			ReplicationSlotRelease();
+			return false;
+		}
+
+		/* Slot not ready yet, let's attempt to make it sync-ready now. */
+		if (sync_state == SYNCSLOT_STATE_INITIATED)
+		{
+			/*
+			 * Wait for the primary server to catch-up. Refer to the comment
+			 * atop the file for details on this wait.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+				TransactionIdPrecedes(remote_slot->catalog_xmin,
+									  slot->data.catalog_xmin))
+			{
+				if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+				{
+					ReplicationSlotRelease();
+					return false;
+				}
+			}
+
+			/*
+			 * Wait for primary is over, update the lsns and mark the slot as
+			 * READY for further syncs.
+			 */
+			local_slot_update(remote_slot);
+			SpinLockAcquire(&slot->mutex);
+			slot->data.sync_state = SYNCSLOT_STATE_READY;
+			SpinLockRelease(&slot->mutex);
+
+			/* Save the changes */
+			ReplicationSlotMarkDirty();
+			ReplicationSlotSave();
+			slot_updated = true;
+
+			ereport(LOG,
+					errmsg("newly locally created slot \"%s\" is sync-ready now",
+						   remote_slot->name));
+		}
+		/* Slot ready for sync, so sync it. */
+		else if (sync_state == SYNCSLOT_STATE_READY)
+		{
+			/*
+			 * Sanity check: With hot_standby_feedback enabled and
+			 * invalidations handled appropriately as above, this should never
+			 * happen.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn)
+				elog(ERROR,
+					 "not synchronizing local slot \"%s\" LSN(%X/%X)"
+					 " to remote slot's LSN(%X/%X) as synchronization "
+					 " would move it backwards", remote_slot->name,
+					 LSN_FORMAT_ARGS(slot->data.restart_lsn),
+					 LSN_FORMAT_ARGS(remote_slot->restart_lsn));
+
+			if (remote_slot->confirmed_lsn != slot->data.confirmed_flush ||
+				remote_slot->restart_lsn != slot->data.restart_lsn ||
+				remote_slot->catalog_xmin != slot->data.catalog_xmin)
+			{
+				/* Update LSN of slot to remote slot's current position */
+				local_slot_update(remote_slot);
+				ReplicationSlotSave();
+				slot_updated = true;
+			}
+		}
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+
+		/* Skip creating the local slot if remote_slot is invalidated already */
+		if (remote_slot->invalidated != RS_INVAL_NONE)
+			return false;
+
+		/* Ensure that we have transaction env needed by get_database_oid() */
+		Assert(IsTransactionState());
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover,
+							  SYNCSLOT_STATE_INITIATED);
+
+		/* For shorter lines. */
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * Wait for the primary server to catch-up. Refer to the comment atop
+		 * the file for details on this wait.
+		 */
+		if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  slot->data.catalog_xmin))
+		{
+			bool		wait_attempts_exceeded = false;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &wait_attempts_exceeded))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid i.e wait_attempts_exceeded is true) and attempt
+				 * the wait and synchronization in the next cycle.
+				 */
+				if (wait_attempts_exceeded)
+				{
+					ReplicationSlotPersist();
+					slot_updated = true;
+				}
+
+				ReplicationSlotRelease();
+				return slot_updated;
+			}
+		}
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&slot->mutex);
+		slot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&slot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		slot_updated = true;
+
+		ereport(LOG,
+				errmsg("newly locally created slot \"%s\" is sync-ready now",
+					   remote_slot->name));
+	}
+
+	ReplicationSlotRelease();
+
+	return slot_updated;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and updates
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns TRUE if any of the slots gets updated in this sync-cycle.
+ */
+static bool
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *tupslot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	ListCell   *lc;
+	bool		some_slot_updated = false;
+
+	/*
+	 * The primary_slot_name is not set yet or WALs not received yet.
+	 * Synchronization is not possible if the walreceiver is not started.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		return false;
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	StartTransactionCommand();
+
+	initStringInfo(&s);
+
+	/* Construct query to fetch slots with failover enabled. */
+	appendStringInfo(&s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover");
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(tupslot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(tupslot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(tupslot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(tupslot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(tupslot, 5,
+																	   &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(tupslot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(tupslot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(tupslot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(tupslot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(tupslot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(lc, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		some_slot_updated |= synchronize_one_slot(wrconn, remote_slot);
+	}
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	CommitTransactionCommand();
+
+	return some_slot_updated;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker",
+							&err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Checks the primary server info.
+ *
+ * Using the specified primary server connection, check whether we are a
+ * cascading standby. It also validates primary_slot_name for non-cascading
+ * standbys.
+ */
+static void
+check_primary_info(WalReceiverConn *wrconn, bool *am_cascading_standby)
+{
+#define PRIMARY_INFO_OUTPUT_COL_COUNT 2
+	WalRcvExecResult *res;
+	Oid			slotRow[PRIMARY_INFO_OUTPUT_COL_COUNT] = {BOOLOID, BOOLOID};
+	StringInfoData cmd;
+	bool		isnull;
+	TupleTableSlot *tupslot;
+	bool		valid;
+	bool		remote_in_recovery;
+	bool		tuple_ok PG_USED_FOR_ASSERTS_ONLY;
+
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	StartTransactionCommand();
+
+	Assert(am_cascading_standby != NULL);
+
+	*am_cascading_standby = false;	/* overwritten later if cascading */
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT pg_is_in_recovery(), count(*) = 1"
+					 " FROM pg_replication_slots"
+					 " WHERE slot_type='physical' AND slot_name=%s",
+					 quote_literal_cstr(PrimarySlotName));
+
+	res = walrcv_exec(wrconn, cmd.data, PRIMARY_INFO_OUTPUT_COL_COUNT, slotRow);
+	pfree(cmd.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch primary_slot_name \"%s\" info from the "
+						"primary: %s", PrimarySlotName, res->err)));
+
+	tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	tuple_ok = tuplestore_gettupleslot(res->tuplestore, true, false, tupslot);
+	Assert(tuple_ok);			/* It must return one tuple */
+
+	remote_in_recovery = DatumGetBool(slot_getattr(tupslot, 1, &isnull));
+	Assert(!isnull);
+
+	/* No need to check further, return that we are cascading standby */
+	if (remote_in_recovery)
+	{
+		*am_cascading_standby = true;
+		ExecClearTuple(tupslot);
+		walrcv_clear_result(res);
+		CommitTransactionCommand();
+		return;
+	}
+
+	valid = DatumGetBool(slot_getattr(tupslot, 2, &isnull));
+	Assert(!isnull);
+
+	if (!valid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("exiting from slot synchronization due to bad configuration"),
+		/* translator: second %s is a GUC variable name */
+				 errdetail("The primary slot \"%s\" specified by %s is not valid.",
+						   PrimarySlotName, "primary_slot_name")));
+	ExecClearTuple(tupslot);
+	walrcv_clear_result(res);
+	CommitTransactionCommand();
+}
+
+/*
+ * Check that all necessary GUCs for slot synchronization are set
+ * appropriately. If not, raise an ERROR.
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+	/* Sanity check. */
+	Assert(enable_syncslot);
+
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_slot_name"));
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be enabled.", "hot_standby_feedback"));
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("wal_level must be >= logical."));
+
+	/*
+	 * The primary_conninfo is required to make connection to primary for
+	 * getting slots information.
+	 */
+	if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_conninfo"));
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	*dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (*dbname == NULL)
+		ereport(ERROR,
+
+		/*
+		 * translator: 'dbname' is a specific option; %s is a GUC variable
+		 * name
+		 */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("'dbname' must be specified in %s.", "primary_conninfo"));
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs have changed, exit the worker and
+ * let it get restarted by the postmaster.
+ */
+static void
+slotsync_reread_config(WalReceiverConn *wrconn)
+{
+	char	   *old_primary_conninfo = pstrdup(PrimaryConnInfo);
+	char	   *old_primary_slotname = pstrdup(PrimarySlotName);
+	bool		old_hot_standby_feedback = hot_standby_feedback;
+	bool		conninfo_changed;
+	bool		primary_slotname_changed;
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	conninfo_changed = strcmp(old_primary_conninfo, PrimaryConnInfo) != 0;
+	primary_slotname_changed = strcmp(old_primary_slotname, PrimarySlotName) != 0;
+
+	if (conninfo_changed ||
+		primary_slotname_changed ||
+		(old_hot_standby_feedback != hot_standby_feedback))
+	{
+		ereport(LOG,
+				errmsg("slot sync worker will restart because of "
+					   "a parameter change"));
+		/* The exit code 1 will make postmaster restart this worker */
+		proc_exit(1);
+	}
+
+	pfree(old_primary_conninfo);
+	pfree(old_primary_slotname);
+}
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		walrcv_disconnect(wrconn);
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting down "
+					   "on receiving SIGINT"));
+		proc_exit(0);
+	}
+
+	if (ConfigReloadPending)
+		slotsync_reread_config(wrconn);
+}
+
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = InvalidPid;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+	bool		am_cascading_standby;
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	Assert(SlotSyncWorker->pid == InvalidPid);
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	validate_slotsync_parameters(&dbname);
+
+	/*
+	 * Connect to the database specified by user in primary_conninfo. We need
+	 * a database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/*
+	 * Using the specified primary server connection, check whether we are
+	 * cascading standby and validates primary_slot_name for
+	 * non-cascading-standbys.
+	 */
+	check_primary_info(wrconn, &am_cascading_standby);
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+		TimestampTz now;
+		bool		some_slot_updated;
+
+		ProcessSlotSyncInterrupts(wrconn);
+
+		if (am_cascading_standby)
+		{
+			/*
+			 * Slot synchronization is currently not supported on cascading
+			 * standby. So if we are on the cascading standby, skip the sync
+			 * and take a longer nap before we check again whether we are
+			 * still cascading standby or not.
+			 */
+			naptime = 6 * WORKER_INACTIVITY_NAPTIME_MS; /* 60 sec */
+		}
+		else
+		{
+			some_slot_updated = synchronize_slots(wrconn);
+
+			/*
+			 * If any of the slots get updated in this sync-cycle, use default
+			 * naptime and update 'last_update_time'. But if no activity is
+			 * observed in this sync-cycle, then increase naptime provided
+			 * inactivity time reaches threshold.
+			 */
+			now = GetCurrentTimestamp();
+			if (some_slot_updated)
+				last_update_time = now;
+			else if (TimestampDifferenceExceeds(last_update_time,
+												now, WORKER_INACTIVITY_THRESHOLD_MS))
+				naptime = WORKER_INACTIVITY_NAPTIME_MS;
+		}
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+
+		/*
+		 * One can promote the standby and we can no longer be a cascading
+		 * standby. So recheck here.
+		 */
+		if (am_cascading_standby)
+			check_primary_info(wrconn, &am_cascading_standby);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsLogicalSlotSyncWorker(void)
+{
+	return SlotSyncWorker->pid == MyProcPid;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (SlotSyncWorker->pid == InvalidPid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Wait for it to die. */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long. */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (SlotSyncWorker->pid == InvalidPid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Allocate and initialize slot sync worker shared memory
+ */
+void
+SlotSyncWorkerShmemInit(void)
+{
+	Size		size;
+	bool		found;
+
+	size = sizeof(SlotSyncWorkerCtx);
+	size = MAXALIGN(size);
+
+	SlotSyncWorker = (SlotSyncWorkerCtx *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
+
+	if (!found)
+	{
+		memset(SlotSyncWorker, 0, size);
+		SlotSyncWorker->pid = InvalidPid;
+		SpinLockInit(&SlotSyncWorker->mutex);
+	}
+}
+
+/*
+ * Register the background worker for slots synchronization provided
+ * enable_syncslot is ON.
+ */
+void
+SlotSyncWorkerRegister(void)
+{
+	BackgroundWorker bgw;
+
+	if (!enable_syncslot)
+	{
+		ereport(LOG,
+				(errmsg("skipping slot synchronization"),
+				 errdetail("enable_syncslot is disabled.")));
+		return;
+	}
+
+	memset(&bgw, 0, sizeof(bgw));
+
+	/* We need database connection which needs shared-memory access as well. */
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+
+	/* Start as soon as a consistent state has been reached in a hot standby */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState_HotStandby;
+
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "replication slot sync worker");
+	snprintf(bgw.bgw_type, BGW_MAXLEN,
+			 "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
+	bgw.bgw_notify_pid = 0;
+	bgw.bgw_main_arg = (Datum) 0;
+
+	RegisterBackgroundWorker(&bgw);
+}
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e46a1955e8..7b3784c212 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -141,7 +141,20 @@
  * subscribe to the new primary without losing any data.
  *
  * However, we do not enable failover for slots created by the table sync
- * worker.
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby due to the following reasons:
+ *
+ * - The standby needs to wait for the primary server to catch up because the
+ *   local restart_lsn of the newly created slot on the standby is set using
+ *   the latest redo position (GetXLogReplayRecPtr()), which is typically ahead
+ *   of the primary's restart_lsn.
+ * - The table sync slot's restart_lsn won't be advanced until the state
+ *   becomes SUBREL_STATE_CATCHUP.
+ *
+ * Therefore, if a failover happens before the restart_lsn advances, the table
+ * sync slot will not be synced to the standby. Consequently, we will not be
+ * able to subscribe to the promoted standby due to the absence of the
+ * necessary table sync slot.
  *
  * Additionally, failover is not enabled for the main slot if the table sync is
  * in progress. This is because if a failover occurs while the table sync
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 8fb0578cda..29afb959a0 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -48,6 +48,7 @@
 #include "pgstat.h"
 #include "postmaster/interrupt.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "replication/walsender_private.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
@@ -264,16 +265,22 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     user will only get commit prepared.
  * failover: If enabled, allows the slot to be synced to physical standbys so
  *     that logical replication can be resumed after failover.
+ * sync_state: Defines slot synchronization state. This function is expected
+ *     to receive either SYNCSLOT_STATE_NONE for the user created slots or
+ *     SYNCSLOT_STATE_INITIATED for the slots being synchronized on the physical
+ *     standby.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover)
+					  bool two_phase, bool failover, char sync_state)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
 
 	Assert(MyReplicationSlot == NULL);
+	Assert(sync_state == SYNCSLOT_STATE_NONE ||
+		   sync_state == SYNCSLOT_STATE_INITIATED);
 
 	ReplicationSlotValidateName(name, ERROR);
 
@@ -329,6 +336,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = sync_state;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -688,12 +696,23 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary server.")));
+
 	ReplicationSlotDropAcquired();
 }
 
@@ -713,6 +732,17 @@ ReplicationSlotAlter(const char *name, bool failover)
 				errmsg("cannot use %s with a physical replication slot",
 					   "ALTER_REPLICATION_SLOT"));
 
+	/*
+	 * Do not allow users to alter the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot alter replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary server.")));
+
 	SpinLockAcquire(&MyReplicationSlot->mutex);
 	MyReplicationSlot->data.failover = failover;
 	SpinLockRelease(&MyReplicationSlot->mutex);
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index e6ffb048b2..2ae4590957 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -44,7 +44,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false);
+						  false, SYNCSLOT_STATE_NONE);
 
 	if (immediately_reserve)
 	{
@@ -137,7 +137,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover);
+						  failover, SYNCSLOT_STATE_NONE);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index ca5b796457..94ac40da88 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1223,7 +1223,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false);
+							  false, false, SYNCSLOT_STATE_NONE);
 
 		if (reserve_wal)
 		{
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover);
+							  two_phase, failover, SYNCSLOT_STATE_NONE);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1406,7 +1406,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, true);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 706140eb9f..11a0465ea1 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -38,6 +38,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -342,6 +343,7 @@ CreateOrAttachShmemStructs(void)
 	WalSummarizerShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7298a187d1..d87020821c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3286,6 +3286,17 @@ ProcessInterrupts(void)
 			 */
 			proc_exit(1);
 		}
+		else if (IsLogicalSlotSyncWorker())
+		{
+			elog(DEBUG1,
+				"replication slot sync worker is shutting down due to administrator command");
+
+			/*
+			 * Slot sync worker can be stopped at any time.
+			 * Use exit status 1 so the background worker is restarted.
+			 */
+			proc_exit(1);
+		}
 		else if (IsBackgroundWorker)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 20ca7669a3..d8caf8554d 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index df5b347f4b..5d1817487a 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -68,6 +68,7 @@
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2033,6 +2034,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_POSTMASTER, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 22a7d1093e..022a205008 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -363,6 +363,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = off			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5e683fdde7..9b9ad266e3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index e90ff376a6..8559900b70 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -79,6 +79,7 @@ typedef enum
 	BgWorkerStart_PostmasterStart,
 	BgWorkerStart_ConsistentState,
 	BgWorkerStart_RecoveryFinished,
+	BgWorkerStart_ConsistentState_HotStandby,
 } BgWorkerStartTime;
 
 #define BGW_DEFAULT_RESTART_INTERVAL	60
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index bbd71d0b42..945d2608f6 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -22,6 +22,7 @@ extern void TablesyncWorkerMain(Datum main_arg);
 
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
+extern bool IsLogicalSlotSyncWorker(void);
 
 extern void HandleParallelApplyMessageInterrupt(void);
 extern void HandleParallelApplyMessages(void);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 1d987c7072..3f2dff4845 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -52,6 +52,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +120,15 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Synchronization state for a logical slot.
+	 *
+	 * The standby can have any value among the possible values of 'i','r' and
+	 * 'n'. For primary, the default is 'n' for all slots but may also be 'r'
+	 * if leftover from a promoted standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)? Only
 	 * relevant for logical slots on the primary server.
@@ -225,9 +242,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover);
+								  bool two_phase, bool failover,
+								  char sync_state);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index f1135762fb..259d0f7065 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -279,6 +279,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -403,6 +418,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -428,6 +444,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 84bb79ac0f..9406a2666f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -237,6 +237,11 @@ extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
+/* Slot sync worker objects */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+extern PGDLLIMPORT bool enable_syncslot;
+
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
@@ -326,6 +331,12 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
+extern void SlotSyncWorkerRegister(void);
+extern void ShutDownSlotSync(void);
+extern void slotsync_drop_initiated_slots(void);
+extern void SlotSyncWorkerShmemInit(void);
+
 #define isParallelApplyWorker(worker) ((worker)->in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
 #define isTablesyncWorker(worker) ((worker)->in_use && \
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
index 81a47c7af8..cda99e9791 100644
--- a/src/test/recovery/t/050_standby_failover_slots_sync.pl
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -297,5 +297,150 @@ is( $primary->safe_psql(
 	'logical slot has failover true on the primary');
 
 $subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
+$primary->safe_psql('postgres', "TRUNCATE tab_int");
+
+##################################################
+# Test logical failover slots on the standby
+# Configure standby1 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub1_slot->| ----> subscriber1 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb1_slot--->| ----> standby1 (connected via streaming replication)
+#                                        |                 lsub1_slot(synced_slot)
+##################################################
+
+my $connstr_1 = $primary->connstr;
+$standby1->stop;
+$standby1->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb1_slot'
+});
+$standby1->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+my $standby1_conninfo = $standby1->connstr . ' dbname=postgres';
+
+# Wait for the standby to start sync
+$offset = -s $standby1->logfile;
+$standby1->start;
+$standby1->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();
+	 SELECT pg_log_standby_snapshot();
+	 SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby1->logfile;
+$standby1->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub1_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby and is sync ready
+is($standby1->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}),
+	"t|r",
+	'logical slot has failover as true and sync_state as ready on standby');
+
+##################################################
+# Test that a synchronized slot can not be decoded, altered and dropped by the user
+##################################################
+
+# Disable hot_standby_feedback temporarily to stop slot sync worker otherwise
+# the concerned testing scenarios here may be interrupted by different error:
+# 'ERROR:  replication slot is active for PID ..'
+
+$standby1->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = off;');
+$standby1->restart;
+
+# Dropping of synced slot should result in error
+my ($result1, $stdout, $stderr) = $standby1->psql('postgres',
+	"SELECT pg_drop_replication_slot('lsub1_slot');");
+ok($stderr =~ /ERROR:  cannot drop replication slot "lsub1_slot"/,
+	"synced slot on standby cannot be dropped");
+
+# Logical decoding on synced slot should result in error
+($result1, $stdout, $stderr) = $standby1->psql('postgres',
+	"select * from pg_logical_slot_get_changes('lsub1_slot',NULL,NULL);");
+ok($stderr =~ /ERROR:  cannot use replication slot "lsub1_slot" for logical decoding/,
+	"logical decoding is not allowed on synced slot");
+
+($result, $stdout, $stderr) = $standby1->psql(
+    'postgres',
+    qq[ALTER_REPLICATION_SLOT lsub1_slot (failover);],
+    replication => 'database');
+ok($stderr =~ /ERROR:  cannot alter replication slot "lsub1_slot"/,
+	"synced slot on standby cannot be altered");
+
+# Enable hot_standby_feedback and restart standby
+$standby1->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = on;');
+$standby1->restart;
+
+##################################################
+# Create another slot which stays in sync_state as initiated ('i')
+# because it's a manually created slot and its lsn is not advanced.
+##################################################
+
+# Create a logical slot with failover = true
+$primary->psql('postgres',
+		q{SELECT pg_create_logical_replication_slot('logical_slot','pgoutput', false, true, true);});
+
+# Wait for the standby to start sync
+$offset = -s $standby1->logfile;
+$standby1->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"logical_slot\"/,
+	$offset);
+
+# Confirm that the logical slot is created on the standby and is in sync initiated state
+  is($standby1->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'logical_slot';}),
+	"t|i",
+	'logical slot has failover as true and sync_state as initiated on standby');
+
+##################################################
+# Promote the standby1 to primary. Confirm that:
+# a) the sync-ready('r') slot 'lsub1_slot' is retained on the new primary
+# b) the initiated('i') slot 'logical_slot' is dropped on promotion
+# c) logical replication for regress_mysub1 is resumed successfully after failover
+##################################################
+$standby1->promote;
+
+# Update subscription with the new primary's connection info
+$subscriber1->safe_psql('postgres',
+	"ALTER SUBSCRIPTION regress_mysub1 DISABLE;
+	 ALTER SUBSCRIPTION regress_mysub1 CONNECTION '$standby1_conninfo';
+	 ALTER SUBSCRIPTION regress_mysub1 ENABLE; ");
+
+is($standby1->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots WHERE slot_name in ('logical_slot','lsub1_slot');}),
+	'lsub1_slot',
+	'synced slot retained on the new primary');
+
+# Insert data on the new primary
+$primary_row_count = 10;
+$standby1->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+$standby1->wait_for_catchup('regress_mysub1');
+
+# Confirm that data in tab_int replicated on subscriber
+is( $subscriber1->safe_psql('postgres', q{SELECT count(*) FROM tab_int;}),
+	"$primary_row_count",
+	'data replicated from the new primary');
 
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 611deeaae5..2dbd7a1243 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2321,6 +2321,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2579,6 +2580,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorkerCtx
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.30.0.windows.2



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-18 23:20                                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-19 06:06                                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-19 11:40                                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-19 13:05                                                                     ` RE: Synchronizing slots from primary to standby Hayato Kuroda (Fujitsu) <[email protected]>
  2023-12-20 05:52                                                                       ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-20 08:03                                                                         ` RE: Synchronizing slots from primary to standby Hayato Kuroda (Fujitsu) <[email protected]>
  2023-12-20 12:42                                                                           ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-12-21 04:24                                                                             ` Peter Smith <[email protected]>
  1 sibling, 0 replies; 113+ messages in thread

From: Peter Smith @ 2023-12-21 04:24 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; +Cc: Hayato Kuroda (Fujitsu) <[email protected]>; Amit Kapila <[email protected]>; shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Here is a minor comment for v51-0001

======
src/backend/replication/slot.c

1.
+void
+RereadConfigAndReInitSlotList(List **standby_slots)
+{
+ char    *pre_standby_slot_names;
+
+ /*
+ * If we are running on a standby, there is no need to reload
+ * standby_slot_names since we do not support syncing slots to cascading
+ * standbys.
+ */
+ if (RecoveryInProgress())
+ {
+ ProcessConfigFile(PGC_SIGHUP);
+ return;
+ }
+
+ pre_standby_slot_names = pstrdup(standby_slot_names);
+
+ ProcessConfigFile(PGC_SIGHUP);
+
+ if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+ {
+ list_free(*standby_slots);
+ *standby_slots = GetStandbySlotList(true);
+ }
+
+ pfree(pre_standby_slot_names);
+}

Consider below, which seems a simpler way to do that but with just one
return point and without duplicating the ProcessConfigFile calls:

SUGGESTION
{
char *pre_standby_slot_names = pstrdup(standby_slot_names);

ProcessConfigFile(PGC_SIGHUP);

if (!RecoveryInProgress())
{
  if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
  {
    list_free(*standby_slots);
    *standby_slots = GetStandbySlotList(true);
  }
}

pfree(pre_standby_slot_names);
}

======
Kind Regards,
Peter Smith.
Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-18 23:20                                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-19 06:06                                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-19 11:40                                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-19 13:05                                                                     ` RE: Synchronizing slots from primary to standby Hayato Kuroda (Fujitsu) <[email protected]>
@ 2023-12-20 12:43                                                                       ` Zhijie Hou (Fujitsu) <[email protected]>
  1 sibling, 0 replies; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-12-20 12:43 UTC (permalink / raw)
  To: Hayato Kuroda (Fujitsu) <[email protected]>; 'shveta malik' <[email protected]>; Amit Kapila <[email protected]>; +Cc: Peter Smith <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Tuesday, December 19, 2023 9:05 PM Kuroda, Hayato/é»’ç”° 隼人 <[email protected]> wrote:
> 
> Dear Shveta,
> 
> I resumed to review the patch. I will play more about it, but I can post some
> cosmetic comments.

Thanks for the comments.

> 
> ====
> walsender.c
> 
> 01. WalSndWaitForStandbyConfirmation
> 
> ```
> +        sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
> ```
> 
> It works well, but I'm not sure whether we should use
> WalSndComputeSleeptime()
> because the function won't be called by walsender.

Changed to a hard-coded value.

> 
> 02.WalSndWaitForStandbyConfirmation
> 
> ```
> +        ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv,
> sleeptime,
> +
> WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
> ```
> 
> Hmm, is it OK to use the same event as WalSndWaitForWal()? IIUC it should be
> avoided.

As discussed, I change the event name to a more common one,
so that it makes sense to use it in both places.

> 
> 03. WalSndShmemInit()
> 
> ```
> +
> +        ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
> ```
> 
> Unnecessary blank?

Removed.

> 
> ~~~~~
> 050_standby_failover_slots_sync.pl
> 
> 04. General
> 
> My pgperltidy modified your test. Please check.

Will run this in next version.

> 
> 05.
> 
> ```
> # Create publication on the primary
> ```
> 
> Missing "a" before publication?

Changed.

> 
> 06.
> 
> ```
> $subscriber1->init(allows_streaming => 'logical');
> ...
> $subscriber2->init(allows_streaming => 'logical');
> ```
> 
> IIUC, these settings are not needed.

Yeah, removed.

> 
> 07.
> 
> ```
> my $primary_insert_time = time();
> ```
> 
> The variable is not used.

Removed.

> 
> 08.
> 
> ```
> # Stop the standby associated with the specified physical replication slot so
> # that the logical replication slot won't receive changes until the standby
> # slot's restart_lsn is advanced or the slot is removed from the
> # standby_slot_names list
> ```
> 
> Missing comma?

Added.

> 
> 09.
> 
> ```
> $back_q->query_until(qr//,
> 	"SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL);\n");
> ```
> 
> Not sure, should we have to close the back_q connection?

Added the quit.

> 
> 10.
> 
> ```
> # Remove the standby from the standby_slot_names list and reload the
> # configuration
> $primary->adjust_conf('postgresql.conf', 'standby_slot_names', "''");
> $primary->psql('postgres', "SELECT pg_reload_conf()");
> ```
> a.
> Missing comma?
> 
> b.
> I counted and reload function in perl (e.g., `$primary->reload;`) is more often
> to
> be used. Do you have a reason to use pg_reload_conf()?

I think it was copied from other places, changed to ->reload.

> 
> 11.
> 
> ```
> # Now that the standby lsn has advanced, the primary must send the decoded
> # changes to the subscription.
> $publisher->wait_for_catchup('regress_mysub1');
> ```
> 
> Is the comment correct? I think primary sends data because the GUC is
> modified.

Fixed.

> 
> 12.
> 
> ```
> # Put the standby back on the primary_slot_name for the rest of the tests
> $primary->adjust_conf('postgresql.conf', 'standby_slot_names', 'sb1_slot');
> $primary->restart();
> ```
> 
> Just to confirm - you used restart() here because we must ensure the GUC
> change is
> propagated to all backends, right?

Yes, but I think restart is not necessary, so I changed it to reload.

> 
> ~~~~~
> wait_event_names.txt
> 
> 13.
> 
> ```
> +WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION    "Waiting for the
> WAL to be received by physical standby in WAL sender process."
> ```
> 
> But there is a possibility that backend processes may wait with the event, right?

Adjusted.

Best Regards,
Hou zj


^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-18 23:20                                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-19 06:06                                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-19 11:40                                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-20 06:31                                                                     ` Peter Smith <[email protected]>
  2023-12-21 09:36                                                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Peter Smith @ 2023-12-20 06:31 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Here are some comments for the patch v50-0002.

======
GENERAL

(I made a short study of all the ereports in this patch -- here are
some findings)

~~~

0.1 Don't need the parentheses.

Checking all the ereports I see that half of them have the redundant
parentheses and half of them do not; You might as well make them all
use the new style where the extra parentheses are not needed.

e.g.
+ ereport(LOG,
+ (errmsg("skipping slot synchronization"),
+ errdetail("enable_syncslot is disabled.")));

e.g.
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot drop replication slot \"%s\"", name),
+ errdetail("This slot is being synced from the primary server.")));

and many more like this. Search for all the ereports.

~~~

0.2
+ ereport(LOG,
+ (errmsg("dropped replication slot \"%s\" of dbid %d as it "
+ "was not sync-ready", NameStr(s->data.name),
+ s->data.database)));

I felt maybe that could be:

errmsg("dropped replication slot \"%s\" of dbid %d", ...
errdetail("It was not sync-ready.")

(now this shares the same errmsg with another ereport)

~~~

0.3.
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("skipping sync of slot \"%s\" as it is a user created"
+ " slot", remote_slot->name),
+ errdetail("This slot has failover enabled on the primary and"
+    " thus is sync candidate but user created slot with"
+    " the same name already exists on the standby.")));

This seemed too wordy. Can't it be shortened (maybe like below)
without losing any of the vital information?

errmsg("skipping sync of slot \"%s\"", ...)
errdetail("A user-created slot with the same name already exists on
the standby.")

~~~

0.4
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("exiting from slot synchronization due to bad configuration"),
+ /* translator: second %s is a GUC variable name */
+ errdetail("The primary slot \"%s\" specified by %s is not valid.",
+    PrimarySlotName, "primary_slot_name")));

/The primary slot/The primary server slot/

~~~

0.5
+ ereport(ERROR,
+ (errmsg("could not fetch primary_slot_name \"%s\" info from the "
+ "primary: %s", PrimarySlotName, res->err)));

/primary:/primary server:/

~~~

0.6
The continuations for long lines are inconsistent. Sometimes there are
trailing spaces and sometimes there are leading spaces. And sometimes
there are both at the same time which would cause double-spacing in
the message! Please make them all the same. I think using leading
spaces is easier but YMMV.

e.g.
+ elog(ERROR,
+ "not synchronizing local slot \"%s\" LSN(%X/%X)"
+ " to remote slot's LSN(%X/%X) as synchronization "
+ " would move it backwards", remote_slot->name,
+ LSN_FORMAT_ARGS(slot->data.restart_lsn),
+ LSN_FORMAT_ARGS(remote_slot->restart_lsn));

======
src/backend/replication/logical/slotsync.c

1. check_primary_info

+ /* No need to check further, return that we are cascading standby */
+ if (remote_in_recovery)
+ {
+ *am_cascading_standby = true;
+ ExecClearTuple(tupslot);
+ walrcv_clear_result(res);
+ CommitTransactionCommand();
+ return;
+ }
+
+ valid = DatumGetBool(slot_getattr(tupslot, 2, &isnull));
+ Assert(!isnull);
+
+ if (!valid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("exiting from slot synchronization due to bad configuration"),
+ /* translator: second %s is a GUC variable name */
+ errdetail("The primary slot \"%s\" specified by %s is not valid.",
+    PrimarySlotName, "primary_slot_name")));
+ ExecClearTuple(tupslot);
+ walrcv_clear_result(res);
+ CommitTransactionCommand();
+}

Now that there is a common cleanup/return code this function be
reduced further like below:

SUGGESTION

if (remote_in_recovery)
{
  /* No need to check further, return that we are cascading standby */
  *am_cascading_standby = true;
}
else
{
  /* We are a normal standby. */

  valid = DatumGetBool(slot_getattr(tupslot, 2, &isnull));
  Assert(!isnull);

  if (!valid)
    ...
}

ExecClearTuple(tupslot);
walrcv_clear_result(res);
CommitTransactionCommand();
}

~~~

2. ReplSlotSyncWorkerMain

+ /*
+ * One can promote the standby and we can no longer be a cascading
+ * standby. So recheck here.
+ */
+ if (am_cascading_standby)
+ check_primary_info(wrconn, &am_cascading_standby);

Minor rewording of that new comment.

SUGGESTION
If the standby was promoted then what was previously a cascading
standby might no longer be one, so recheck each time.

======
src/test/recovery/t/050_verify_slot_order.pl

3.
+##################################################
+# Test that a synchronized slot can not be decoded, altered and
dropped by the user
+##################################################

/and dropped/or dropped/

~~~

4.
+
+($result, $stdout, $stderr) = $standby1->psql(
+    'postgres',
+    qq[ALTER_REPLICATION_SLOT lsub1_slot (failover);],
+    replication => 'database');
+ok($stderr =~ /ERROR:  cannot alter replication slot "lsub1_slot"/,
+ "synced slot on standby cannot be altered");
+

Add a comment for this test  part

SUGGESTION
Attempting to alter a synced slot should result in an error

~~~

5.
IMO it would be better if the tests were done in the same order
mentioned in the comment. So either change the tests or change the
comment.

======
Kind Regards,
Peter Smith.
Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-18 23:20                                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-19 06:06                                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-19 11:40                                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-20 06:31                                                                     ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
@ 2023-12-21 09:36                                                                       ` shveta malik <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: shveta malik @ 2023-12-21 09:36 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; +Cc: Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Wed, Dec 20, 2023 at 12:02 PM Peter Smith <[email protected]> wrote:
>
> Here are some comments for the patch v50-0002.

Thank You for the feedback. I have addressed these in v52.

> ======
> GENERAL
>
> (I made a short study of all the ereports in this patch -- here are
> some findings)
>
> ~~~
>
> 0.1 Don't need the parentheses.
>
> Checking all the ereports I see that half of them have the redundant
> parentheses and half of them do not; You might as well make them all
> use the new style where the extra parentheses are not needed.
>
> e.g.
> + ereport(LOG,
> + (errmsg("skipping slot synchronization"),
> + errdetail("enable_syncslot is disabled.")));
>
> e.g.
> + ereport(ERROR,
> + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> + errmsg("cannot drop replication slot \"%s\"", name),
> + errdetail("This slot is being synced from the primary server.")));
>
> and many more like this. Search for all the ereports.
>
> ~~~
>
> 0.2
> + ereport(LOG,
> + (errmsg("dropped replication slot \"%s\" of dbid %d as it "
> + "was not sync-ready", NameStr(s->data.name),
> + s->data.database)));
>
> I felt maybe that could be:
>
> errmsg("dropped replication slot \"%s\" of dbid %d", ...
> errdetail("It was not sync-ready.")
>
> (now this shares the same errmsg with another ereport)
>
> ~~~
>
> 0.3.
> + ereport(ERROR,
> + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> + errmsg("skipping sync of slot \"%s\" as it is a user created"
> + " slot", remote_slot->name),
> + errdetail("This slot has failover enabled on the primary and"
> +    " thus is sync candidate but user created slot with"
> +    " the same name already exists on the standby.")));
>
> This seemed too wordy. Can't it be shortened (maybe like below)
> without losing any of the vital information?
>
> errmsg("skipping sync of slot \"%s\"", ...)
> errdetail("A user-created slot with the same name already exists on
> the standby.")

I have modified it a little bit more. Please see now. I wanted to add
the info that slot-sync worker is exiting instead of skipping a slot
and that the concerned slot is a failover slot on primary. These were
the other comments around the same.

> ~~~
>
> 0.4
> + ereport(ERROR,
> + (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> + errmsg("exiting from slot synchronization due to bad configuration"),
> + /* translator: second %s is a GUC variable name */
> + errdetail("The primary slot \"%s\" specified by %s is not valid.",
> +    PrimarySlotName, "primary_slot_name")));
>
> /The primary slot/The primary server slot/
>
> ~~~
>
> 0.5
> + ereport(ERROR,
> + (errmsg("could not fetch primary_slot_name \"%s\" info from the "
> + "primary: %s", PrimarySlotName, res->err)));
>
> /primary:/primary server:/
>
> ~~~
>
> 0.6
> The continuations for long lines are inconsistent. Sometimes there are
> trailing spaces and sometimes there are leading spaces. And sometimes
> there are both at the same time which would cause double-spacing in
> the message! Please make them all the same. I think using leading
> spaces is easier but YMMV.
>
> e.g.
> + elog(ERROR,
> + "not synchronizing local slot \"%s\" LSN(%X/%X)"
> + " to remote slot's LSN(%X/%X) as synchronization "
> + " would move it backwards", remote_slot->name,
> + LSN_FORMAT_ARGS(slot->data.restart_lsn),
> + LSN_FORMAT_ARGS(remote_slot->restart_lsn));
>
> ======
> src/backend/replication/logical/slotsync.c
>
> 1. check_primary_info
>
> + /* No need to check further, return that we are cascading standby */
> + if (remote_in_recovery)
> + {
> + *am_cascading_standby = true;
> + ExecClearTuple(tupslot);
> + walrcv_clear_result(res);
> + CommitTransactionCommand();
> + return;
> + }
> +
> + valid = DatumGetBool(slot_getattr(tupslot, 2, &isnull));
> + Assert(!isnull);
> +
> + if (!valid)
> + ereport(ERROR,
> + (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> + errmsg("exiting from slot synchronization due to bad configuration"),
> + /* translator: second %s is a GUC variable name */
> + errdetail("The primary slot \"%s\" specified by %s is not valid.",
> +    PrimarySlotName, "primary_slot_name")));
> + ExecClearTuple(tupslot);
> + walrcv_clear_result(res);
> + CommitTransactionCommand();
> +}
>
> Now that there is a common cleanup/return code this function be
> reduced further like below:
>
> SUGGESTION
>
> if (remote_in_recovery)
> {
>   /* No need to check further, return that we are cascading standby */
>   *am_cascading_standby = true;
> }
> else
> {
>   /* We are a normal standby. */
>
>   valid = DatumGetBool(slot_getattr(tupslot, 2, &isnull));
>   Assert(!isnull);
>
>   if (!valid)
>     ...
> }
>
> ExecClearTuple(tupslot);
> walrcv_clear_result(res);
> CommitTransactionCommand();
> }
>
> ~~~
>
> 2. ReplSlotSyncWorkerMain
>
> + /*
> + * One can promote the standby and we can no longer be a cascading
> + * standby. So recheck here.
> + */
> + if (am_cascading_standby)
> + check_primary_info(wrconn, &am_cascading_standby);
>
> Minor rewording of that new comment.
>
> SUGGESTION
> If the standby was promoted then what was previously a cascading
> standby might no longer be one, so recheck each time.
>
> ======
> src/test/recovery/t/050_verify_slot_order.pl
>
> 3.
> +##################################################
> +# Test that a synchronized slot can not be decoded, altered and
> dropped by the user
> +##################################################
>
> /and dropped/or dropped/
>
> ~~~
>
> 4.
> +
> +($result, $stdout, $stderr) = $standby1->psql(
> +    'postgres',
> +    qq[ALTER_REPLICATION_SLOT lsub1_slot (failover);],
> +    replication => 'database');
> +ok($stderr =~ /ERROR:  cannot alter replication slot "lsub1_slot"/,
> + "synced slot on standby cannot be altered");
> +
>
> Add a comment for this test  part
>
> SUGGESTION
> Attempting to alter a synced slot should result in an error
>
> ~~~
>
> 5.
> IMO it would be better if the tests were done in the same order
> mentioned in the comment. So either change the tests or change the
> comment.
>
> ======
> Kind Regards,
> Peter Smith.
> Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-14 04:45                                                       ` Peter Smith <[email protected]>
  2023-12-14 05:53                                                         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-15 05:42                                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  1 sibling, 2 replies; 113+ messages in thread

From: Peter Smith @ 2023-12-14 04:45 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

A review comment for v47-0001

======
src/backend/replication/slot.c

1.  GetStandbySlotList

+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+ char    *pre_standby_slot_names;
+
+ ProcessConfigFile(PGC_SIGHUP);
+
+ /*
+ * If we are running on a standby, there is no need to reload
+ * standby_slot_names since we do not support syncing slots to cascading
+ * standbys.
+ */
+ if (RecoveryInProgress())
+ return;

Should the RecoveryInProgress() check be first -- even before the
ProcessConfigFile call?

======
Kind Regards,
Peter Smith.
Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 04:45                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
@ 2023-12-14 05:53                                                         ` Zhijie Hou (Fujitsu) <[email protected]>
  1 sibling, 0 replies; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-12-14 05:53 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Thursday, December 14, 2023 12:45 PM Peter Smith <[email protected]> wrote:

> A review comment for v47-0001

Thanks for the comment.

> 
> ======
> src/backend/replication/slot.c
> 
> 1.  GetStandbySlotList
> 
> +static void
> +WalSndRereadConfigAndReInitSlotList(List **standby_slots) {
> + char    *pre_standby_slot_names;
> +
> + ProcessConfigFile(PGC_SIGHUP);
> +
> + /*
> + * If we are running on a standby, there is no need to reload
> + * standby_slot_names since we do not support syncing slots to
> + cascading
> + * standbys.
> + */
> + if (RecoveryInProgress())
> + return;
> 
> Should the RecoveryInProgress() check be first -- even before the
> ProcessConfigFile call?

ProcessConfigFile is necessary here, it is used not only for standby_slot_names
but also all other GUCs that could be used in the caller.

Best Regards,
Hou zj


^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 04:45                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
@ 2023-12-15 05:42                                                         ` shveta malik <[email protected]>
  2023-12-15 12:25                                                           ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-15 05:42 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; +Cc: Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Thu, Dec 14, 2023 at 10:15 AM Peter Smith <[email protected]> wrote:
>
> A review comment for v47-0001
>

Thanks for reviewing. I have addressed these in v48. There is some
design change around the code part where we were checking cascading
and were revalidating new GUC values on conf-reload. So code has
changed entirely around that part where some of these comments were.
Please review now and let me know.

> ======
> src/backend/replication/slot.c
>
> 1.  GetStandbySlotList
>
> +static void
> +WalSndRereadConfigAndReInitSlotList(List **standby_slots)
> +{
> + char    *pre_standby_slot_names;
> +
> + ProcessConfigFile(PGC_SIGHUP);
> +
> + /*
> + * If we are running on a standby, there is no need to reload
> + * standby_slot_names since we do not support syncing slots to cascading
> + * standbys.
> + */
> + if (RecoveryInProgress())
> + return;
>
> Should the RecoveryInProgress() check be first -- even before the
> ProcessConfigFile call?
>
> ======
> Kind Regards,
> Peter Smith.
> Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 04:45                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-15 05:42                                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-15 12:25                                                           ` Nisha Moond <[email protected]>
  2023-12-17 02:51                                                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Nisha Moond @ 2023-12-15 12:25 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Peter Smith <[email protected]>; Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Review for v47 patch -

(1)
When we try to create a subscription on standby using a synced slot
that is in 'r' sync_state, the subscription will be created at the
subscriber, and on standby, two actions will take place -
    (i)  As copy_data is true by default, it will switch the failover
state of the synced-slot to 'false'.
    (ii) As we don't allow to use synced-slots, it will start giving
the expected error in the log file -
        ERROR:  cannot use replication slot "logical_slot" for logical decoding
        DETAIL:  This slot is being synced from the primary server.
        HINT:  Specify another replication slot.

The first one seems an issue,  it toggles the failover to false and
then it remains false after that. I think it should be fixed.

(2)
With the patch, the 'CREATE SUBSCRIPTION' command with a 'slot_name'
of an 'active' logical slot fails and errors out -
    ERROR:  could not alter replication slot "logical_slot" on
publisher: ERROR:  replication slot "logical_slot1" is active for PID
xxxx

Without the patch, the create subscription with an 'active' slot_name
succeeds and the log file shows the error "could not start WAL
streaming: ERROR:  replication slot "logical_slot" is active for PID
xxxx".

Given that the specified active slot_name has failover set to false
and the create subscription command also specifies failover=false, the
expected behavior of the "with-patch" case is anticipated to be the
same as that of the "without-patch" scenario.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-14 04:45                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-12-15 05:42                                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-15 12:25                                                           ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
@ 2023-12-17 02:51                                                             ` Amit Kapila <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Amit Kapila @ 2023-12-17 02:51 UTC (permalink / raw)
  To: Nisha Moond <[email protected]>; +Cc: shveta malik <[email protected]>; Peter Smith <[email protected]>; Drouvot, Bertrand <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Fri, Dec 15, 2023 at 5:55 PM Nisha Moond <[email protected]> wrote:
>
> (1)
> When we try to create a subscription on standby using a synced slot
> that is in 'r' sync_state, the subscription will be created at the
> subscriber, and on standby, two actions will take place -
>     (i)  As copy_data is true by default, it will switch the failover
> state of the synced-slot to 'false'.
>     (ii) As we don't allow to use synced-slots, it will start giving
> the expected error in the log file -
>         ERROR:  cannot use replication slot "logical_slot" for logical decoding
>         DETAIL:  This slot is being synced from the primary server.
>         HINT:  Specify another replication slot.
>
> The first one seems an issue,  it toggles the failover to false and
> then it remains false after that. I think it should be fixed.
>

+1. If we don't allow the slot to be used, we shouldn't allow its
state to be changed as well.

> (2)
> With the patch, the 'CREATE SUBSCRIPTION' command with a 'slot_name'
> of an 'active' logical slot fails and errors out -
>     ERROR:  could not alter replication slot "logical_slot" on
> publisher: ERROR:  replication slot "logical_slot1" is active for PID
> xxxx
>
> Without the patch, the create subscription with an 'active' slot_name
> succeeds and the log file shows the error "could not start WAL
> streaming: ERROR:  replication slot "logical_slot" is active for PID
> xxxx".
>
> Given that the specified active slot_name has failover set to false
> and the create subscription command also specifies failover=false, the
> expected behavior of the "with-patch" case is anticipated to be the
> same as that of the "without-patch" scenario.
>

Currently, we first acquire the slot to change its state but I guess
if we want the behavior as you mentioned we first need to check the
slot's 'failover' state without acquiring the slot. I am not sure if
that is any better because anyway we are going to fail in the very
next step as the slot is busy.

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-12-12 09:46                                               ` Zhijie Hou (Fujitsu) <[email protected]>
  1 sibling, 0 replies; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-12-12 09:46 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; Amit Kapila <[email protected]>; shveta malik <[email protected]>; +Cc: Nisha Moond <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Monday, December 11, 2023 3:52 PM Drouvot, Bertrand <[email protected]> wrote:

Hi,

> On 12/8/23 10:06 AM, Amit Kapila wrote:
> > On Wed, Dec 6, 2023 at 4:53 PM shveta malik <[email protected]>
> wrote:
> >>
> >> PFA v43, changes are:
> >>
> >
> > I wanted to discuss 0003 patch about cascading standby's. It is not
> > clear to me whether we want to allow physical standbys to further wait
> > for cascading standby to sync their slots. If we allow such a feature
> > one may expect even primary to wait for all the cascading standby's
> > because otherwise still logical subscriber can be ahead of one of the
> > cascading standby.
> 
> I've the same feeling here. I think it would probably be expected that the
> primary also wait for all the cascading standby.
> 
> > I feel even if we want to allow such a behaviour we can do it later
> > once the main feature is committed.
> 
> Agree.
> 
> > I think it would
> > be good to just allow logical walsenders on primary to wait for
> > physical standbys represented by GUC 'standby_slot_names'.
> 
> That makes sense for me for v1.
> 
> > If we agree
> > on that then it would be good to prohibit setting this GUC on standby
> > or at least it should be a no-op even if this GUC should be set on
> > physical standby.
> 
> I'd prefer to completely prohibit it on standby (to make it very clear it's not
> working at all) as long as one can enable it without downtime once the standby
> is promoted (which is the case currently).

I think we could not check if we are in a standby server in the GUC check_hook,
because the XLogCtl(which is checked in RecoveryInProgress) may have not been
initialized yet. Besides, other GUCs like synchronous_standby_names also don't
work on standby but it will be no-op. So I feel we can also ignore
standby_slot_names on standby. What do you think ?

Here is the V46 patch set which changed the following things:

V46-0001:
* Address Peter[1] and Amit's[2] comments.
* Fix one CFbot failure in meson build.
* Ignore the standby_slot_names on a standby server since we don't support
  syncing slots to cascade standby.

V46-0002:
1) Fix for CFBot make warning.
2) Cascading support removal. Now we do not need to check 'sync_state != 'i''
   in the query while fetching failover slots. This check was needed on the
   cascading standby to fetch failover slots from the first standby.
3) Test correction and optimization.

0003 patch is removed since we agreed not to support syncing slots to cascading
standby.

Thanks Shveta for working on the changes in V46-0002 and thanks Ajin for
working on the test optimization.

--
TODO

There are few pending comments that mentioned in [3][4][5] which are still in
progress.

[1] https://www.postgresql.org/message-id/CAHut%2BPsf9z132WNgy0Gr10ZTnonpNjvTBj74wG8kSxXU4rOD7g%40mail.g...
[2] https://www.postgresql.org/message-id/CAA4eK1%2BCXpfiTLbYRaOoUBP9Z1-xJZdX6QOp14rCdaF5E2gsgQ%40mail.g...
[3] https://www.postgresql.org/message-id/CAJpy0uDaGMNpgmdxie-MgHmMhnD4ET_LDjQNEe76xJ%2BMLqRQ8Q%40mail.g...
[4] https://www.postgresql.org/message-id/CAJpy0uDcOf5Hvk_CdCCAbfx9SY%2Bog%3D%3D%3DtgiuhWKzkYyqebui9g%40...
[5] https://www.postgresql.org/message-id/CAJpy0uC-8mrn6jakcFjSVmbJiHZs-Okq8YKxGfrMLPD-2%3DwOqQ%40mail.g...

Best Regards,
Hou zj


Attachments:

  [application/octet-stream] v46-0002-Add-logical-slot-sync-capability-to-the-physical.patch (90.4K, ../../OS0PR01MB57168930BF2E0D281A724A69948EA@OS0PR01MB5716.jpnprd01.prod.outlook.com/2-v46-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From 7ed6b58df1e6a71b766d202fbd1eae66e08194f8 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Tue, 12 Dec 2023 16:55:06 +0800
Subject: [PATCH v46 2/2] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover. All the failover logical
replication slots on the primary (assuming configurations are
appropriate) are automatically created on the physical standbys and
are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The nap time of the worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. If
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated.

If a logical slot on the primary is valid but is invalidated on the standby,
then that slot is dropped and recreated on the standby in next sync-cycle. It
is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently). This situation may occur due to the
following reasons:
- The max_slot_wal_keep_size on the standby is insufficient to retain WAL
  records from the restart_lsn of the slot.
- primary_slot_name is temporarily reset to null and the physical slot is
  removed.
- The primary changes wal_level to a level lower than logical.

The slots synchronization status on the standby can be monitored using
'sync_state' column of pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but slot is not ready yet for periodic syncs,
'r': ready for periodic syncs.
---
 doc/src/sgml/bgworker.sgml                    |   64 +-
 doc/src/sgml/config.sgml                      |   32 +-
 doc/src/sgml/logicaldecoding.sgml             |   33 +
 doc/src/sgml/system-views.sgml                |   26 +
 src/backend/access/transam/xlogrecovery.c     |   10 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    4 +
 src/backend/postmaster/postmaster.c           |   10 +
 .../libpqwalreceiver/libpqwalreceiver.c       |   43 +-
 src/backend/replication/logical/Makefile      |    1 +
 src/backend/replication/logical/logical.c     |   25 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1282 +++++++++++++++++
 src/backend/replication/logical/tablesync.c   |    1 +
 src/backend/replication/logical/worker.c      |   15 +-
 src/backend/replication/slot.c                |   24 +-
 src/backend/replication/slotfuncs.c           |   37 +-
 src/backend/replication/walsender.c           |    6 +-
 src/backend/storage/ipc/ipci.c                |    2 +
 src/backend/tcop/postgres.c                   |   11 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   10 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/commands/subscriptioncmds.h       |    4 +
 src/include/postmaster/bgworker.h             |    1 +
 src/include/replication/logicalworker.h       |    1 +
 src/include/replication/slot.h                |   22 +-
 src/include/replication/walreceiver.h         |   19 +
 src/include/replication/worker_internal.h     |   11 +
 .../t/050_standby_failover_slots_sync.pl      |  203 +++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    2 +
 34 files changed, 1888 insertions(+), 36 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2c393385a9..ecde4fa61f 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -114,18 +114,58 @@ typedef struct BackgroundWorker
 
   <para>
    <structfield>bgw_start_time</structfield> is the server state during which
-   <command>postgres</command> should start the process; it can be one of
-   <literal>BgWorkerStart_PostmasterStart</literal> (start as soon as
-   <command>postgres</command> itself has finished its own initialization; processes
-   requesting this are not eligible for database connections),
-   <literal>BgWorkerStart_ConsistentState</literal> (start as soon as a consistent state
-   has been reached in a hot standby, allowing processes to connect to
-   databases and run read-only queries), and
-   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system has
-   entered normal read-write state).  Note the last two values are equivalent
-   in a server that's not a hot standby.  Note that this setting only indicates
-   when the processes are to be started; they do not stop when a different state
-   is reached.
+   <command>postgres</command> should start the process. Note that this setting
+   only indicates when the processes are to be started; they do not stop when
+   a different state is reached. Possible values are:
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>BgWorkerStart_PostmasterStart</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_PostmasterStart</primary></indexterm>
+       Start as soon as postgres itself has finished its own initialization;
+       processes requesting this are not eligible for database connections.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState</primary></indexterm>
+       Start as soon as a consistent state has been reached in a hot-standby,
+       allowing processes to connect to databases and run read-only queries.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_RecoveryFinished</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_RecoveryFinished</primary></indexterm>
+       Start as soon as the system has entered normal read-write state. Note
+       that the <literal>BgWorkerStart_ConsistentState</literal> and
+      <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
+       in a server that's not a hot standby.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState_HotStandby</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState_HotStandby</primary></indexterm>
+       Same meaning as <literal>BgWorkerStart_ConsistentState</literal> but
+       it is more strict in terms of the server i.e. start the worker only
+       if it is hot-standby.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
   </para>
 
   <para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 7993fe3cdd..e14953e4dd 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4373,6 +4373,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must configure
+        <varname>enable_syncslot</varname> = true so they can receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4568,8 +4574,12 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           <varname>primary_conninfo</varname> string, or in a separate
           <filename>~/.pgpass</filename> file on the standby server (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          If slot synchronization is enabled then it is also necessary to
+          specify <literal>dbname</literal> in the
+          <varname>primary_conninfo</varname> string. This will only be used for
+          slot synchronization. It is ignored for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4894,6 +4904,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..43537e00b2 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,39 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and setting
+     <varname>enable_syncslot</varname> on the standby. For the synchronization
+     to work, it is mandatory to have a physical replication slot between the
+     primary and the standby. It's highly recommended that the said physical
+     replication slot is listed in <varname>standby_slot_names</varname> on
+     the primary to prevent the subscriber from consuming changes faster than
+     the hot standby. Additionally, <varname>hot_standby_feedback</varname>
+     must be enabled on the standby for the slots synchronization to work.
+    </para>
+
+    <para>
+     By enabling synchronization of slots, logical replication can be resumed
+     after failover depending upon the
+     <link linkend="view-pg-replication-slots">pg_replication_slots</link>.<structfield>sync_state</structfield>
+     for the synchronized slots on the standby at the time of failover.
+     Only slots that were in ready sync_state ('r') on the standby before
+     failover can be used for logical replication after failover.
+     However, the slots which were in initiated sync_state ('i') and
+     not sync-ready ('r') at the time of failover will be dropped and
+     logical replication for such slots can not be resumed after failover.
+     This applies to the case where a logical subscription is disabled
+     before failover and is enabled after failover. If the synchronized
+     slot due to disabled subscription could not be made sync-ready ('r')
+     on standby, then the subscription can not be resumed after failover
+     even when enabled.
+     If the primary is idle, then the synchronized slots on the standby may
+     take a noticeable time to reach the ready ('r') sync_state. This can
+     be sped up by calling the
+     <function>pg_log_standby_snapshot</function> function on the primary.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..c906f2186e 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,32 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has configured <varname>enable_syncslot</varname> = true
+      </para>
+      <para>
+       State code:
+       <literal>n</literal> = none for user created slots,
+       <literal>i</literal> = sync initiated for the slot but slot is not ready
+        yet for periodic syncs,
+       <literal>r</literal> = ready for periodic syncs.
+      </para>
+      <para>
+      The hot standby can have any of these sync_state for the slots but on a
+      hot standby, the slots with state 'r' and 'i' can neither be used for
+      logical decoding nor dropped by the user. The primary server will have
+      sync_state as 'n' for all the slots. But if the standby is promoted to
+      become the new primary server, sync_state can be seen 'r' as well. On
+      this new primary server, slots with sync_state as 'r' and 'n' will
+      behave the same.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index a2c8fa3981..c867cfbc63 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1436,15 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 3c99cf6047..7f74f53ad1 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -21,6 +21,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "replication/worker_internal.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -129,6 +130,9 @@ static const struct
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 651b85ea74..9b74a49663 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -115,6 +115,7 @@
 #include "postmaster/syslogger.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -1003,6 +1004,12 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	/*
+	 * Register the slot sync worker here to kick start slot-sync operation
+	 * sooner on the physical standby.
+	 */
+	SlotSyncWorkerRegister();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
@@ -5740,6 +5747,9 @@ bgworker_should_start_now(BgWorkerStartTime start_time)
 		case PM_HOT_STANDBY:
 			if (start_time == BgWorkerStart_ConsistentState)
 				return true;
+			if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
+					 pmState != PM_RUN)
+				return true;
 			/* fall through */
 
 		case PM_RECOVERY:
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index ff65fdb4d9..a3c2290147 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -418,6 +421,44 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
@@ -997,7 +1038,7 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
  */
 static void
 libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
-					bool failover)
+					 bool failover)
 {
 	StringInfoData cmd;
 	PGresult   *res;
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..c21d081346 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,31 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	if (RecoveryInProgress())
+	{
+		/*
+		 * Do not allow consumption of a "synchronized" slot until the standby
+		 * gets promoted.
+		 */
+		if (slot->data.sync_state != SYNCSLOT_STATE_NONE)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot use replication slot \"%s\" for logical "
+						"decoding", NameStr(slot->data.name)),
+					 errdetail("This slot is being synced from the primary server."),
+					 errhint("Specify another replication slot.")));
+	}
+	else
+	{
+		/*
+		 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+		 * promotion.
+		 */
+		if (slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			elog(ERROR, "replication slot \"%s\" was not synced completely "
+				"from the primary server", NameStr(slot->data.name));
+	}
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..553492058e
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1282 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * While creating the slot on physical standby, if the local restart_lsn and/or
+ * local catalog_xmin is ahead of those on the remote then the worker cannot
+ * create the local slot in sync with the primary server because that would
+ * mean moving the local slot backwards and the standby might not have WALs
+ * retained for old LSN. In this case, the worker will wait for the primary
+ * server slot's restart_lsn and catalog_xmin to catch up with the local one
+ * before attempting the actual sync. Meanwhile, it will persist the slot with
+ * sync_state as SYNCSLOT_STATE_INITIATED('i'). Once the primary server catches
+ * up, it will move the slot to SYNCSLOT_STATE_READY('r') state and will perform
+ * the sync periodically.
+ *
+ * The worker also takes care of dropping the slots which were created by it
+ * and are currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/table.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_database.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/*
+ * Struct for sharing information between startup process and slot
+ * sync worker.
+ *
+ * Slot sync worker's pid is needed by startup process in order to
+ * shut it down during promotion.
+ */
+typedef struct SlotSyncWorkerCtx
+{
+	pid_t		pid;
+	slock_t		mutex;
+} SlotSyncWorkerCtx;
+
+SlotSyncWorkerCtx *SlotSyncWorker = NULL;
+
+/* GUC variable */
+bool		enable_syncslot = false;
+
+/* The last sync-cycle time when the worker updated any of the slots. */
+static TimestampTz last_update_time;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn);
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * If passed, *wait_attempts_exceeded will be set to true only if this
+ * function exits after exhausting its wait attempts. It will be false
+ * in all the other cases like failure, remote-slot invalidation, primary
+ * could catch up.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *wait_attempts_exceeded)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
+
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn,"
+					 " confirmed_flush_lsn, catalog_xmin"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		XLogRecPtr	new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *slot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Handle any termination request if any */
+		ProcessSlotSyncInterrupts(wrconn);
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot \"%s\" info from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" creation aborted", remote_slot->name),
+					 errdetail("This slot was not found on the primary server")));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(slot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" creation aborted", remote_slot->name),
+					 errdetail("This slot was invalidated on the primary server")));
+			pfree(cmd.data);
+			ExecClearTuple(slot);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * Having got a valid restart_lsn, the confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(slot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\"",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			if (wait_attempts_exceeded)
+				*wait_attempts_exceeded = true;
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up (refer
+ * to the comment atop the file for details on this wait)
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			local_slots = lappend(local_slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(local_slots);
+}
+
+/*
+ * Get list of local logical slots which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slots(void)
+{
+	List	   *local_slots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			local_slots = lappend(local_slots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return local_slots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+check_sync_slot_on_remote(ReplicationSlot *local_slot, List *remote_slots,
+						  bool *locally_invalidated)
+{
+	ListCell   *lc;
+
+	foreach(lc, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			SpinLockAcquire(&local_slot->mutex);
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+			SpinLockRelease(&local_slot->mutex);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Additionally, it drops slots that are valid on the primary but got
+ * invalidated on the standby. This situation may occur due to the following
+ * reasons:
+ * - The max_slot_wal_keep_size on the standby is insufficient to retain WAL
+ *   records from the restart_lsn of the slot.
+ * - primary_slot_name is temporarily reset to null and the physical slot is
+ *   removed.
+ * - The primary changes wal_level to a level lower than logical.
+ *
+ * The assumption is that these dropped local invalidated slots will get
+ * recreated in next sync-cycle and it is okay to drop and recreate such slots
+ * as long as these are not consumable on the standby (which is the case
+ * currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	local_slots = get_local_synced_slots();
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc);
+		bool		remote_exists = false;
+		bool		locally_invalidated = false;
+
+		remote_exists = check_sync_slot_on_remote(local_slot, remote_slot_list,
+												  &locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!remote_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ */
+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+					 bool *slot_updated)
+{
+	ReplicationSlot *s;
+	ReplicationSlot *slot;
+	char		sync_state = '\0';
+
+	/*
+	 * Sanity check: Make sure that concerned WAL is received before syncing
+	 * slot to target lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* Search for the named slot */
+	if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		SpinLockAcquire(&s->mutex);
+		sync_state = s->data.sync_state;
+		SpinLockRelease(&s->mutex);
+
+		/* User created slot with the same name exists, raise ERROR. */
+		if (sync_state == SYNCSLOT_STATE_NONE)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("skipping sync of slot \"%s\" as it is a user created"
+							" slot", remote_slot->name),
+					 errdetail("This slot has failover enabled on the primary and"
+							   " thus is sync candidate but user created slot with"
+							   " the same name already exists on the standby")));
+		}
+	}
+
+	StartTransactionCommand();
+
+	/* Slot created by the slot sync worker exists, sync it */
+	if (sync_state)
+	{
+		Assert(sync_state == SYNCSLOT_STATE_READY ||
+			   sync_state == SYNCSLOT_STATE_INITIATED);
+
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		slot = MyReplicationSlot;
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (slot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&slot->mutex);
+			slot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (slot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/* Slot ready for sync, so sync it. */
+		if (sync_state == SYNCSLOT_STATE_READY)
+		{
+			/*
+			 * Sanity check: With hot_standby_feedback enabled and
+			 * invalidations handled apropriately as above, this should never
+			 * happen.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn)
+			{
+				elog(ERROR,
+					 "not synchronizing local slot \"%s\" LSN(%X/%X)"
+					 " to remote slot's LSN(%X/%X) as synchronization "
+					 " would move it backwards", remote_slot->name,
+					 LSN_FORMAT_ARGS(slot->data.restart_lsn),
+					 LSN_FORMAT_ARGS(remote_slot->restart_lsn));
+			}
+
+			if (remote_slot->confirmed_lsn != slot->data.confirmed_flush ||
+				remote_slot->restart_lsn != slot->data.restart_lsn ||
+				remote_slot->catalog_xmin != slot->data.catalog_xmin)
+			{
+				/* Update LSN of slot to remote slot's current position */
+				local_slot_update(remote_slot);
+				ReplicationSlotSave();
+				*slot_updated = true;
+			}
+		}
+
+		/* Slot not ready yet, let's attempt to make it sync-ready now. */
+		else if (sync_state == SYNCSLOT_STATE_INITIATED)
+		{
+			/*
+			 * Wait for the primary server to catch-up. Refer to the comment
+			 * atop the file for details on this wait.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+				TransactionIdPrecedes(remote_slot->catalog_xmin,
+									  slot->data.catalog_xmin))
+			{
+				if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+					goto cleanup;
+			}
+
+			/*
+			 * Wait for primary is over, update the lsns and mark the slot as
+			 * READY for further syncs.
+			 */
+			local_slot_update(remote_slot);
+			SpinLockAcquire(&slot->mutex);
+			slot->data.sync_state = SYNCSLOT_STATE_READY;
+			SpinLockRelease(&slot->mutex);
+
+			/* Save the changes */
+			ReplicationSlotMarkDirty();
+			ReplicationSlotSave();
+			*slot_updated = true;
+
+			ereport(LOG,
+					errmsg("newly locally created slot \"%s\" is sync-ready now",
+						   remote_slot->name));
+		}
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover,
+							  SYNCSLOT_STATE_INITIATED);
+
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * Wait for the primary server to catch-up. Refer to the comment atop
+		 * the file for details on this wait.
+		 */
+		if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  slot->data.catalog_xmin))
+		{
+			bool		wait_attempts_exceeded = false;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &wait_attempts_exceeded))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid) and attempt the wait and synchronization in
+				 * the next cycle.
+				 */
+				if (wait_attempts_exceeded)
+				{
+					ReplicationSlotPersist();
+					*slot_updated = true;
+				}
+
+				goto cleanup;
+			}
+		}
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&slot->mutex);
+		slot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&slot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		*slot_updated = true;
+
+		ereport(LOG,
+				errmsg("newly locally created slot \"%s\" is sync-ready now",
+					   remote_slot->name));
+	}
+
+cleanup:
+
+	ReplicationSlotRelease();
+	CommitTransactionCommand();
+
+	return;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and updates
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns TRUE if any of the slots gets updated in this sync-cycle.
+ */
+static bool
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *tupslot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	MemoryContext oldctx = CurrentMemoryContext;
+	ListCell   *lc;
+	bool		slot_updated = false;
+
+	/*
+	 * The primary_slot_name is not set yet or WALs not received yet.
+	 * Synchronization is not possible if the walreceiver is not started.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		return slot_updated;
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	StartTransactionCommand();
+
+	/*
+	 * Make result tuples live outside TopTransactionContext to make them
+	 * accessible even after transaction is committed.
+	 */
+	MemoryContextSwitchTo(oldctx);
+
+	initStringInfo(&s);
+
+	/* Construct query to fetch slots with failover enabled. */
+	appendStringInfo(&s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover");
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(tupslot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(tupslot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(tupslot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(tupslot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(tupslot, 5,
+																	   &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(tupslot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(tupslot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(tupslot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(tupslot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(tupslot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(lc, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		synchronize_one_slot(wrconn, remote_slot, &slot_updated);
+	}
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	return slot_updated;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker",
+							&err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Using the specified primary server connection, validate if the
+ * physical slot identified by GUC primary_slot_name exists.
+ *
+ * Exit the worker if the slot is not found.
+ */
+static void
+validate_primary_slot(WalReceiverConn *wrconn)
+{
+	WalRcvExecResult *res;
+	Oid			slotRow[1] = {BOOLOID};
+	StringInfoData cmd;
+	bool		isnull;
+	TupleTableSlot *slot;
+	bool		valid;
+	bool		tuple_ok PG_USED_FOR_ASSERTS_ONLY;
+
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	StartTransactionCommand();
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT count(*) = 1 from pg_replication_slots "
+					 "WHERE slot_type='physical' and slot_name=%s",
+					 quote_literal_cstr(PrimarySlotName));
+
+	res = walrcv_exec(wrconn, cmd.data, 1, slotRow);
+	pfree(cmd.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch primary_slot_name \"%s\" info from the "
+						"primary: %s", PrimarySlotName, res->err)));
+
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	tuple_ok = tuplestore_gettupleslot(res->tuplestore, true, false, slot);
+	Assert(tuple_ok);			/* It must return one tuple */
+
+	valid = DatumGetBool(slot_getattr(slot, 1, &isnull));
+	Assert(!isnull);
+
+	if (!valid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("exiting from slot synchronization due to bad configuration"),
+		/* translator: second %s is a GUC variable name */
+				 errdetail("The primary slot \"%s\" specified by %s is not valid.",
+						   PrimarySlotName, "primary_slot_name")));
+	ExecClearTuple(slot);
+	walrcv_clear_result(res);
+	CommitTransactionCommand();
+}
+
+/*
+ * Checks if GUCs are set appropriately before starting slot sync worker
+ *
+ * The slot sync worker can not start if 'enable_syncslot' is off and
+ * since 'enable_syncslot' is ON, check that the other GUC settings
+ * (primary_slot_name, hot_standby_feedback, wal_level, primary_conninfo)
+ * are compatible with slot synchronization. If not, raise ERROR.
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_slot_name"));
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be enabled.", "hot_standby_feedback"));
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("wal_level must be >= logical."));
+
+	/*
+	 * The primary_conninfo is required to make connection to primary for
+	 * getting slots information.
+	 */
+	if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_conninfo"));
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	*dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (*dbname == NULL)
+		ereport(ERROR,
+
+		/*
+		 * translator: 'dbname' is a specific option; %s is a GUC variable
+		 * name
+		 */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("'dbname' must be specified in %s.", "primary_conninfo"));
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs have changed, re-validate them. The
+ * worker will exit if the check fails.
+ */
+static void
+slotsync_reread_config(WalReceiverConn *wrconn)
+{
+	char	   *old_primary_conninfo = pstrdup(PrimaryConnInfo);
+	char	   *old_primary_slot_name = pstrdup(PrimarySlotName);
+	bool		old_hot_standby_feedback = hot_standby_feedback;
+	bool		revalidate = false;
+	char	   *old_dbname;
+	bool		conninfoChanged;
+	bool		slotnameChanged;
+
+	old_dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	Assert(old_dbname);
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	conninfoChanged = strcmp(old_primary_conninfo, PrimaryConnInfo) != 0;
+	slotnameChanged = strcmp(old_primary_slot_name, PrimarySlotName) != 0;
+
+	revalidate = conninfoChanged || slotnameChanged ||
+		(old_hot_standby_feedback != hot_standby_feedback);
+
+	pfree(old_primary_conninfo);
+	pfree(old_primary_slot_name);
+
+	if (revalidate)
+	{
+		char	   *new_dbname;
+
+		validate_slotsync_parameters(&new_dbname);
+
+		/*
+		 * Since we have initialized this worker with the old dbname, thus
+		 * exit if dbname changed. Let it get restarted and connect to the new
+		 * dbname specified.
+		 */
+		if (conninfoChanged && strcmp(old_dbname, new_dbname) != 0)
+			ereport(ERROR,
+					errmsg("exiting slot sync worker as dbname in "
+						   "primary_conninfo changed"));
+
+		if (slotnameChanged)
+			validate_primary_slot(wrconn);
+	}
+}
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting"
+					   " down on receiving SIGINT"));
+
+		walrcv_disconnect(wrconn);
+		proc_exit(0);
+	}
+
+	if (ConfigReloadPending)
+		slotsync_reread_config(wrconn);
+}
+
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = InvalidPid;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	Assert(SlotSyncWorker->pid == InvalidPid);
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	validate_slotsync_parameters(&dbname);
+
+	/*
+	 * Connect to the database specified by user in primary_conninfo. We need
+	 * a database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/*
+	 * Using the existing primary server connection, validate the slot
+	 * specified in primary_slot_name.
+	 */
+	validate_primary_slot(wrconn);
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+		TimestampTz now;
+		bool		slot_updated;
+
+		ProcessSlotSyncInterrupts(wrconn);
+
+		slot_updated = synchronize_slots(wrconn);
+
+		/*
+		 * If any of the slots get updated in this sync-cycle, use default
+		 * naptime and update 'last_update_time'. But if no activity is
+		 * observed in this sync-cycle, then increase naptime provided
+		 * inactivity time reaches threshold.
+		 */
+		now = GetCurrentTimestamp();
+		if (slot_updated)
+			last_update_time = now;
+		else if (TimestampDifferenceExceeds(last_update_time,
+											now, WORKER_INACTIVITY_THRESHOLD_MS))
+			naptime = WORKER_INACTIVITY_NAPTIME_MS;
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsLogicalSlotSyncWorker(void)
+{
+	return SlotSyncWorker->pid == MyProcPid;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (SlotSyncWorker->pid == InvalidPid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Wait for it to die. */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long. */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (SlotSyncWorker->pid == InvalidPid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Allocate and initialize slot sync worker shared memory
+ */
+void
+SlotSyncWorkerShmemInit(void)
+{
+	Size		size;
+	bool		found;
+
+	size = sizeof(SlotSyncWorkerCtx);
+	size = MAXALIGN(size);
+
+	SlotSyncWorker = (SlotSyncWorkerCtx *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
+
+	if (!found)
+	{
+		memset(SlotSyncWorker, 0, size);
+		SlotSyncWorker->pid = InvalidPid;
+		SpinLockInit(&SlotSyncWorker->mutex);
+	}
+}
+
+/*
+ * Register the background worker for slots synchronization provided
+ * enable_syncslot is ON.
+ */
+void
+SlotSyncWorkerRegister(void)
+{
+	BackgroundWorker bgw;
+
+	if (!enable_syncslot)
+	{
+		ereport(LOG,
+				errmsg("skipping slots synchronization because enable_syncslot is "
+					   "disabled."));
+		return;
+	}
+
+	memset(&bgw, 0, sizeof(bgw));
+
+	/* We need database connection which needs shared-memory access as well. */
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+
+	/* Start as soon as a consistent state has been reached in a hot standby */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState_HotStandby;
+
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "replication slot sync worker");
+	snprintf(bgw.bgw_type, BGW_MAXLEN,
+			 "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
+	bgw.bgw_notify_pid = 0;
+	bgw.bgw_main_arg = (Datum) 0;
+
+	RegisterBackgroundWorker(&bgw);
+}
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 7b6170fe55..64fcfdac9a 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "commands/subscriptioncmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e46a1955e8..7b3784c212 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -141,7 +141,20 @@
  * subscribe to the new primary without losing any data.
  *
  * However, we do not enable failover for slots created by the table sync
- * worker.
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby due to the following reasons:
+ *
+ * - The standby needs to wait for the primary server to catch up because the
+ *   local restart_lsn of the newly created slot on the standby is set using
+ *   the latest redo position (GetXLogReplayRecPtr()), which is typically ahead
+ *   of the primary's restart_lsn.
+ * - The table sync slot's restart_lsn won't be advanced until the state
+ *   becomes SUBREL_STATE_CATCHUP.
+ *
+ * Therefore, if a failover happens before the restart_lsn advances, the table
+ * sync slot will not be synced to the standby. Consequently, we will not be
+ * able to subscribe to the promoted standby due to the absence of the
+ * necessary table sync slot.
  *
  * Additionally, failover is not enabled for the main slot if the table sync is
  * in progress. This is because if a failover occurs while the table sync
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 49d2de5024..a21a57c09c 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -262,11 +263,15 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     user will only get commit prepared.
  * failover: If enabled, allows the slot to be synced to physical standbys so
  *     that logical replication can be resumed after failover.
+ * sync_state: Defines slot synchronization state. For user created slots, it
+ *     is SYNCSLOT_STATE_NONE and for the slots being synchronized on the
+ *     physical standby, it is either SYNCSLOT_STATE_INITIATED or
+ *     SYNCSLOT_STATE_READY.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover)
+					  bool two_phase, bool failover, char sync_state)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -327,6 +332,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = sync_state;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -686,12 +692,26 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+	{
+		ReplicationSlotRelease();
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary server.")));
+	}
+
 	ReplicationSlotDropAcquired();
 }
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index c87f61666d..e5ba5956aa 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -44,7 +44,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false);
+						  false, SYNCSLOT_STATE_NONE);
 
 	if (immediately_reserve)
 	{
@@ -137,7 +137,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover);
+						  failover, SYNCSLOT_STATE_NONE);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fddc9310c6..8d48d7c8f4 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1071,7 +1071,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false);
+							  false, false, SYNCSLOT_STATE_NONE);
 
 		if (reserve_wal)
 		{
@@ -1102,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover);
+							  two_phase, failover, SYNCSLOT_STATE_NONE);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, true);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0e0ac22bdd..ae2daff87b 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -37,6 +37,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -339,6 +340,7 @@ CreateOrAttachShmemStructs(void)
 	WalRcvShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7298a187d1..d87020821c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3286,6 +3286,17 @@ ProcessInterrupts(void)
 			 */
 			proc_exit(1);
 		}
+		else if (IsLogicalSlotSyncWorker())
+		{
+			elog(DEBUG1,
+				"replication slot sync worker is shutting down due to administrator command");
+
+			/*
+			 * Slot sync worker can be stopped at any time.
+			 * Use exit status 1 so the background worker is restarted.
+			 */
+			proc_exit(1);
+		}
 		else if (IsBackgroundWorker)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 9a54e04821..7d50971fd7 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2020,6 +2021,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_POSTMASTER, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5d940b72cd..39224137e1 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -358,6 +358,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = off			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bbc03bb76b..82a11e4a31 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 214dc6c29e..75b4b2040d 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -17,6 +17,7 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"
 
 extern ObjectAddress CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										bool isTopLevel);
@@ -28,4 +29,7 @@ extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
 extern char defGetStreamingMode(DefElem *def);
 
+extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn,
+										char *slotname, bool missing_ok);
+
 #endif							/* SUBSCRIPTIONCMDS_H */
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index e90ff376a6..8559900b70 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -79,6 +79,7 @@ typedef enum
 	BgWorkerStart_PostmasterStart,
 	BgWorkerStart_ConsistentState,
 	BgWorkerStart_RecoveryFinished,
+	BgWorkerStart_ConsistentState_HotStandby,
 } BgWorkerStartTime;
 
 #define BGW_DEFAULT_RESTART_INTERVAL	60
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index bbd71d0b42..945d2608f6 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -22,6 +22,7 @@ extern void TablesyncWorkerMain(Datum main_arg);
 
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
+extern bool IsLogicalSlotSyncWorker(void);
 
 extern void HandleParallelApplyMessageInterrupt(void);
 extern void HandleParallelApplyMessages(void);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 5ddad69348..5a03f8c8bb 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -15,7 +15,6 @@
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "storage/spin.h"
-#include "replication/walreceiver.h"
 
 /*
  * Behaviour of replication slots, upon release or crash.
@@ -52,6 +51,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +119,13 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Is this a slot created by a sync-slot worker?
+	 *
+	 * Only relevant for logical slots on the physical standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)? Only
 	 * relevant for logical slots on the primary server.
@@ -225,9 +239,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover);
+								  bool two_phase, bool failover,
+								  char sync_state);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
@@ -253,7 +268,6 @@ extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_l
 extern int	ReplicationSlotIndex(ReplicationSlot *slot);
 extern bool ReplicationSlotName(int index, Name name);
 extern void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot);
-extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok);
 
 extern void StartupReplicationSlots(void);
 extern void CheckPointReplicationSlots(bool is_shutdown);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index f1135762fb..c96a814b26 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -19,6 +19,7 @@
 #include "pgtime.h"
 #include "port/atomics.h"
 #include "replication/logicalproto.h"
+#include "replication/slot.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/latch.h"
@@ -279,6 +280,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -403,6 +419,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -428,6 +445,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 84bb79ac0f..9406a2666f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -237,6 +237,11 @@ extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
+/* Slot sync worker objects */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+extern PGDLLIMPORT bool enable_syncslot;
+
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
@@ -326,6 +331,12 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
+extern void SlotSyncWorkerRegister(void);
+extern void ShutDownSlotSync(void);
+extern void slotsync_drop_initiated_slots(void);
+extern void SlotSyncWorkerShmemInit(void);
+
 #define isParallelApplyWorker(worker) ((worker)->in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
 #define isTablesyncWorker(worker) ((worker)->in_use && \
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
index bd6affaf48..20f9d825e7 100644
--- a/src/test/recovery/t/050_standby_failover_slots_sync.pl
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -296,4 +296,207 @@ is( $primary->safe_psql(
 
 $subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
 
+##################################################
+# Test logical failover slots on the standby
+# Configure standby3 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub3_slot->| ----> subscriber3 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb3_slot--->| ----> standby3 (connected via streaming replication)
+#                                        |                 lsub3_slot(synced_slot)
+##################################################
+
+# Cleanup old standby_slot_names
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = ''
+));
+$primary->start;
+
+$primary->psql('postgres',
+		q{SELECT pg_create_physical_replication_slot('sb3_slot');});
+
+# Create table and publication on primary
+$primary->safe_psql('postgres', "CREATE TABLE tab_mypub3 (a int PRIMARY KEY);");
+$primary->safe_psql('postgres', "CREATE PUBLICATION mypub3 FOR TABLE tab_mypub3;");
+
+$backup_name = 'backup3';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+my $connstr_1 = $primary->connstr;
+$standby3->stop;
+$standby3->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb3_slot'
+});
+$standby3->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+$standby3->start;
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb3_slot'
+));
+$primary->start;
+
+# Restart the standby
+$standby3->restart;
+my $standby3_conninfo = $standby3->connstr . ' dbname=postgres';
+
+# Create a subscriber node
+my $subscriber3 = PostgreSQL::Test::Cluster->new('subscriber3');
+$subscriber3->init(allows_streaming => 'logical');
+$subscriber3->start;
+$subscriber3->safe_psql('postgres', "CREATE TABLE tab_mypub3 (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true & wait for sync to complete.
+$subscriber3->safe_psql('postgres',
+		"CREATE SUBSCRIPTION mysub3 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION mypub3 WITH (slot_name = lsub3_slot, failover = true);");
+$subscriber3->wait_for_subscription_sync;
+
+# Wait for the standby to start sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub3_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub3_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby and is sync ready
+is($standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub3_slot';}),
+	"t|r",
+	'logical slot has failover as true and sync_state as ready on standby');
+
+##################################################
+# Test to confirm that restart_lsn and confirmed_flush_lsn of the logical slot
+# on the primary is synced to the standby
+##################################################
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_mypub3;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_mypub3 SELECT generate_series(1, 10);");
+
+# Get the restart_lsn for the logical slot lsub3_slot on the primary
+my $primary_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';");
+
+# Confirm that restart_lsn of lsub3_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';]);
+is($result, 't', 'restart_lsn of slot lsub3_slot synced to standby');
+
+# Get the confirmed_flush_lsn for the logical slot lsub3_slot on the primary
+$primary_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';");
+
+# Confirm that confirmed_flush_lsn of lsub3_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' <= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub3_slot synced to the standby');
+
+##################################################
+# Test that synchronized slot can neither be docoded nor dropped by the user
+##################################################
+
+# Disable hot_standby_feedback
+$standby3->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = off;');
+$standby3->restart;
+
+# Dropping of synced slot should result in error
+my ($result1, $stdout, $stderr) = $standby3->psql('postgres',
+	"SELECT pg_drop_replication_slot('lsub3_slot');");
+ok($stderr =~ /ERROR:  cannot drop replication slot "lsub3_slot"/,
+	"synced slot on standby cannot be dropped");
+
+# Logical decoding on synced slot should result in error
+($result1, $stdout, $stderr) = $standby3->psql('postgres',
+	"select * from pg_logical_slot_get_changes('lsub3_slot',NULL,NULL);");
+ok($stderr =~ /ERROR:  cannot use replication slot "lsub3_slot" for logical decoding/,
+	"logical decoding is not allowed on synced slot");
+
+# Enable hot_standby_feedback and restart standby
+$standby3->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = on;');
+$standby3->restart;
+
+##################################################
+# Create another slot which stays in sync_state as initiated ('i')
+##################################################
+
+# Create a logical slot with failover = true
+$primary->psql('postgres',
+		q{SELECT pg_create_logical_replication_slot('logical_slot','pgoutput', false, true, true);});
+
+# Wait for the standby to start sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"logical_slot\"/,
+	$offset);
+
+# Confirm that the logical slot is created on the standby and is in sync initiated state
+  is($standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'logical_slot';}),
+	"t|i",
+	'logical slot has failover as true and sync_state as initiated on standby');
+
+##################################################
+# Promote the standby3 to primary. Confirm that:
+# a) the sync-ready('r') slot 'lsub3_slot' is retained on new primary
+# b) the initiated('i') slot 'logical_slot'is dropped on promotion
+# c) logical replication for mysub3 is resumed succesfully after failover
+##################################################
+
+$standby3->promote;
+
+# Update subscription with new primary's connection info
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 DISABLE;");
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 CONNECTION '$standby3_conninfo';");
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 ENABLE;");
+
+is($standby3->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots WHERE slot_name in ('logical_slot','lsub3_slot');}),
+	'lsub3_slot',
+	'synced slot retained on new primary');
+
+# Insert data on the new primary
+$standby3->safe_psql('postgres',
+	"INSERT INTO tab_mypub3 SELECT generate_series(11, 20);");
+
+# Confirm that data in tab_mypub3 replicated on subscriber
+is( $subscriber3->safe_psql('postgres', q{SELECT count(*) FROM tab_mypub3;}),
+	"20",
+	'data replicated from new primary');
+
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 199863c6b5..10c6f32a30 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2321,6 +2321,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2579,6 +2580,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorkerCtx
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.30.0.windows.2



  [application/octet-stream] v46-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (146.4K, ../../OS0PR01MB57168930BF2E0D281A724A69948EA@OS0PR01MB5716.jpnprd01.prod.outlook.com/3-v46-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From 66c8f5a6c2a5e1ee8a8041413642fb5c7a0157d8 Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Wed, 6 Dec 2023 15:52:16 +0530
Subject: [PATCH v46 1/2] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to
the physical standbys so that logical replication can be resumed after
failover. It is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API.

Ex1:
CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

Ex2: (failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

A new replication command called ALTER_REPLICATION_SLOT and a
corresponding walreceiver API function named walrcv_alter_slot have been
implemented. They allow subscribers or users to modify the failover
property of a replication slot on the publisher.

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).
---
 contrib/test_decoding/expected/slot.out       |  58 +++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |  20 +-
 doc/src/sgml/ref/create_subscription.sgml     |  23 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       | 110 +++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  38 +-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  53 ++-
 src/backend/replication/logical/worker.c      |  67 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 192 +++++++++-
 src/backend/replication/slotfuncs.c           |  25 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 358 +++++++++++++++++-
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_dump/pg_dump.c                     |  20 +-
 src/bin/pg_dump/pg_dump.h                     |   1 +
 src/bin/pg_upgrade/info.c                     |   5 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 .../t/050_standby_failover_slots_sync.pl      | 299 +++++++++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 ++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 50 files changed, 1565 insertions(+), 170 deletions(-)
 create mode 100644 src/test/recovery/t/050_standby_failover_slots_sync.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 44cada2b40..7993fe3cdd 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4360,6 +4360,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..481e397bad 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
@@ -230,6 +233,17 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
+
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property of the new slot may differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription. When creating the slot,
+      ensure the slot failover property matches the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter value of the subscription.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..fa6cd1c43f 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,29 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..67826b89c2 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,34 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If the slot_name is specified without the create_slot option,
+			 * it is possible that the user intends to use an existing slot on
+			 * the publisher, so here we alter the failover property of the
+			 * slot to match the failover value in subscription.
+			 *
+			 * We do not need to change the failover to false if the server
+			 * does not support failover (e.g. pre-PG17)
+			 */
+			else if (opts.slot_name &&
+					 (failover_enabled || walrcv_server_version(wrconn) >= 170000))
+			{
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+								opts.slot_name,
+								failover_enabled ? "true" : "false")));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,13 +1331,22 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
@@ -1334,13 +1395,26 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 						/* translator: %s is an SQL ALTER command */
 								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
 										 isadd ?
@@ -1389,7 +1463,19 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 					ereport(ERROR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "two_phase"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "failover"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 4152d1ddbc..ff65fdb4d9 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -888,8 +892,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -918,7 +922,8 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+			appendStringInfoString(&cmd, "FAILOVER, ");
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -987,6 +992,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 4d056c16c8..7b6170fe55 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -624,15 +624,28 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "two_phase")));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "failover")));
+
 				should_exit = true;
 			}
 		}
@@ -1430,7 +1443,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1732,10 +1746,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1743,9 +1759,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1759,9 +1774,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..e46a1955e8 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,6 +132,33 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * failover = true when creating the subscription. Enabling failover allows us
+ * to smoothly transition to the promoted standby, ensuring that we can
+ * subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
@@ -3947,6 +3974,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4510,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4568,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4608,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..49d2de5024 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -248,10 +260,13 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     during getting changes, if the two_phase option is enabled it can skip
  *     prepare because by that time start decoding point has been moved. So the
  *     user will only get commit prepared.
+ * failover: If enabled, allows the slot to be synced to physical standbys so
+ *     that logical replication can be resumed after failover.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +695,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2200,148 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+	bool		ok;
+
+	/* Need a modifiable copy of string. */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into a list of identifiers. */
+	ok = SplitIdentifierString(rawname, ',', &elemlist);
+
+	if (!ok)
+		GUC_check_errdetail("List syntax is invalid.");
+
+	/*
+	 * If there is a syntax error in the name or if the replication slots'
+	 * data is not initialized yet (i.e., we are in the startup process), skip
+	 * the slot verification.
+	 */
+	if (!ok || !ReplicationSlotCtl)
+	{
+		pfree(rawname);
+		list_free(elemlist);
+		return ok;
+	}
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			GUC_check_errdetail("replication slot \"%s\" does not exist",
+								name);
+			ok = false;
+			break;
+		}
+
+		if (!SlotIsPhysical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			ok = false;
+			break;
+		}
+	}
+
+	pfree(rawname);
+	list_free(elemlist);
+	return ok;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	/*
+	 * Since we do not support syncing slots to cascading standbys, we return
+	 * NIL here if we are running in a standby to indicate that no standby
+	 * slots need to be waited for.
+	 */
+	if (RecoveryInProgress())
+		return NIL;
+
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..c87f61666d 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -491,6 +501,12 @@ pg_logical_replication_slot_advance(XLogRecPtr moveto)
 											   .segment_close = wal_segment_close),
 									NULL, NULL, NULL);
 
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to moveto lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(moveto);
+
 		/*
 		 * Start reading at the slot's restart_lsn, which we know to point to
 		 * a valid record.
@@ -679,6 +695,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +751,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +791,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 26ded928a7..ca61a99785 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -387,7 +387,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..fddc9310c6 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool		failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,257 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up the logical walsender processes with failover-enabled slots if the
+ * physical slot of the current walsender is specified in standby_slot_names
+ * GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names;
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	/*
+	 * If we are running on a standby, there is no need to reload
+	 * standby_slot_names since we do not support syncing slots to cascading
+	 * standbys.
+	 */
+	if (RecoveryInProgress())
+		return;
+
+	pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn. Additionally,
+ * it removes slots that have been invalidated, dropped, or converted to
+ * logical slots.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			/*
+			 * It may happen that the slot specified in standby_slot_names GUC
+			 * value is dropped, so let's skip over it.
+			 */
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+		}
+		else if (SlotIsLogical(slot))
+		{
+			/*
+			 * If a logical slot name is provided in standby_slot_names, issue
+			 * a WARNING and skip it. Although logical slots are disallowed in
+			 * the GUC check_hook(validate_standby_slots), it is still
+			 * possible for a user to drop an existing physical slot and
+			 * recreate a logical slot with the same name. Since it is
+			 * harmless, a WARNING should be enough, no need to error-out.
+			 */
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+		}
+		else
+		{
+			SpinLockAcquire(&slot->mutex);
+
+			if (slot->data.invalidated != RS_INVAL_NONE)
+			{
+				/*
+				 * Specified physical slot have been invalidated, so no point
+				 * in waiting for it.
+				 */
+				warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+			}
+			else if (XLogRecPtrIsInvalid(slot->data.restart_lsn) ||
+					 slot->data.restart_lsn < wait_for_lsn)
+			{
+				bool	inactive = (slot->active_pid == 0);
+
+				SpinLockRelease(&slot->mutex);
+
+				/* Log warning if no active_pid for this physical slot */
+				if (inactive)
+					ereport(WARNING,
+							errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+								   name, "standby_slot_names"),
+							errdetail("Logical replication is waiting on the "
+									  "standby associated with \"%s\".", name),
+							errhint("Consider starting standby associated with "
+									"\"%s\" or amend standby_slot_names.", name));
+
+				/* Continue if the current slot hasn't caught up. */
+				continue;
+			}
+			else
+			{
+				Assert(slot->data.restart_lsn >= wait_for_lsn);
+			}
+
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving the given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	if (standby_slots == NIL)
+		return;
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, we also
+ * wait for all the specified streaming replication standby servers to
+ * confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr even when we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
-	if (RecentFlushPtr != InvalidXLogRecPtr &&
-		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	if (!XLogRecPtrIsInvalid(RecentFlushPtr) && loc <= RecentFlushPtr)
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know
+		 * we have enough WAL available and all the standby servers have
+		 * confirmed receipt of WAL up to RecentFlushPtr. This is particularly
+		 * interesting if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1849,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1864,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,9 +1903,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
+		{
+			/* Already caught up and doesn't need to wait for standby_slots. */
 			break;
+		}
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
 		WalSndCaughtUp = true;
@@ -1654,9 +1954,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2121,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2358,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3621,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3692,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another
+	 * CV that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f7c9882f7c..9a54e04821 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4571,6 +4571,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..5d940b72cd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -329,6 +329,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8c0b5486b9..373c2514df 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4618,6 +4618,7 @@ getSubscriptions(Archive *fout)
 	int			i_subsynccommit;
 	int			i_subpublications;
 	int			i_suborigin;
+	int			i_subfailoverstate;
 	int			i,
 				ntups;
 
@@ -4673,14 +4674,22 @@ getSubscriptions(Archive *fout)
 		appendPQExpBufferStr(query,
 							 " s.subpasswordrequired,\n"
 							 " s.subrunasowner,\n"
-							 " s.suborigin\n");
+							 " s.suborigin,\n");
 	else
 		appendPQExpBuffer(query,
 						  " 't' AS subpasswordrequired,\n"
 						  " 't' AS subrunasowner,\n"
-						  " '%s' AS suborigin\n",
+						  " '%s' AS suborigin,\n",
 						  LOGICALREP_ORIGIN_ANY);
 
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(query,
+							 " s.subfailoverstate\n");
+	else
+		appendPQExpBuffer(query,
+						  " '%c' AS subfailoverstate\n",
+						  LOGICALREP_FAILOVER_STATE_DISABLED);
+
 	appendPQExpBufferStr(query,
 						 "FROM pg_subscription s\n"
 						 "WHERE s.subdbid = (SELECT oid FROM pg_database\n"
@@ -4709,6 +4718,7 @@ getSubscriptions(Archive *fout)
 	i_subsynccommit = PQfnumber(res, "subsynccommit");
 	i_subpublications = PQfnumber(res, "subpublications");
 	i_suborigin = PQfnumber(res, "suborigin");
+	i_subfailoverstate = PQfnumber(res, "subfailoverstate");
 
 	subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo));
 
@@ -4746,6 +4756,8 @@ getSubscriptions(Archive *fout)
 		subinfo[i].subpublications =
 			pg_strdup(PQgetvalue(res, i, i_subpublications));
 		subinfo[i].suborigin = pg_strdup(PQgetvalue(res, i, i_suborigin));
+		subinfo[i].subfailoverstate =
+			pg_strdup(PQgetvalue(res, i, i_subfailoverstate));
 
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(subinfo[i].dobj), fout);
@@ -4771,6 +4783,7 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	int			npubnames = 0;
 	int			i;
 	char		two_phase_disabled[] = {LOGICALREP_TWOPHASE_STATE_DISABLED, '\0'};
+	char		failover_disabled[] = {LOGICALREP_FAILOVER_STATE_DISABLED, '\0'};
 
 	/* Do nothing in data-only dump */
 	if (dopt->dataOnly)
@@ -4818,6 +4831,9 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	if (strcmp(subinfo->subtwophasestate, two_phase_disabled) != 0)
 		appendPQExpBufferStr(query, ", two_phase = on");
 
+	if (strcmp(subinfo->subfailoverstate, failover_disabled) != 0)
+		appendPQExpBufferStr(query, ", failover = true");
+
 	if (strcmp(subinfo->subdisableonerr, "t") == 0)
 		appendPQExpBufferStr(query, ", disable_on_error = true");
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2fe3cbed9a..f62a4dfd4b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -671,6 +671,7 @@ typedef struct _SubscriptionInfo
 	char	   *subsynccommit;
 	char	   *subpublications;
 	char	   *suborigin;
+	char	   *subfailoverstate;
 } SubscriptionInfo;
 
 /*
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..e16286f18c 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,6 +679,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
@@ -687,6 +688,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +699,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..2d8bcb26f9 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced to the
+								 * physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 020e7aa1cc..cb3a2b3aed 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -159,7 +159,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -179,8 +179,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 049801186c..905964a2e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3327,7 +3327,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 77e8b13764..bbc03bb76b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..5ddad69348 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)? Only
+	 * relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 949e874f21..f1135762fb 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -355,9 +355,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									  const char *slotname,
+									  bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -399,6 +410,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -428,8 +440,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 60313980a9..ecd212c7b6 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -12,6 +12,8 @@
 #ifndef _WALSENDER_H
 #define _WALSENDER_H
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -45,6 +47,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index db73408937..84bb79ac0f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -256,7 +256,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..083b558448 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_standby_failover_slots_sync.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
new file mode 100644
index 0000000000..bd6affaf48
--- /dev/null
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -0,0 +1,299 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+##################################################
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+##################################################
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on the primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres',
+	"CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+
+# Create a table and a subscription with failover = true
+$subscriber1->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);
+]);
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	CREATE SUBSCRIPTION regress_mysub2 CONNECTION '$publisher_connstr' PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);
+]);
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with the specified physical replication slot so
+# that the logical replication slot won't receive changes until the standby
+# comes up.
+$standby1->stop;
+
+# Create some data on the primary
+my $primary_row_count   = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('regress_mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary until standby1 acknowledges changes"
+);
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 acknowledges changes");
+
+# Stop the standby associated with the specified physical replication slot so
+# that the logical replication slot won't receive changes until the standby
+# slot's restart_lsn is advanced or the slot is removed from the
+# standby_slot_names list
+$publisher->safe_psql('postgres', "TRUNCATE tab_int;");
+$publisher->wait_for_catchup('regress_mysub1');
+$standby1->stop;
+
+##################################################
+# Verify that when using pg_logical_slot_get_changes to consume changes from a
+# logical slot with failover enabled, it will also wait for the slots specified
+# in standby_slot_names to catch up.
+##################################################
+
+# Create a logical 'test_decoding' replication slot with failover enabled
+$publisher->safe_psql('postgres',
+	"SELECT pg_create_logical_replication_slot('test_slot', 'test_decoding', false, false, true);"
+);
+
+my $back_q = $primary->background_psql('postgres', on_error_stop => 0);
+my $pid = $back_q->query('SELECT pg_backend_pid()');
+
+# Try and get changes from the logical slot with failover enabled.
+my $offset = -s $primary->logfile;
+$back_q->query_until(qr//,
+	"SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL);\n");
+
+# Wait until the primary server logs a warning indicating that it is waiting
+# for the sb1_slot to catch up.
+$primary->wait_for_log(
+	qr/WARNING: ( [A-Z0-9]+:)? replication slot \"sb1_slot\" specified in parameter \"standby_slot_names\" does not have active_pid/,
+	$offset);
+
+ok($primary->safe_psql('postgres', "SELECT pg_cancel_backend($pid)"),
+	"cancelling pg_logical_slot_get_changes command");
+
+$publisher->safe_psql('postgres',
+	"SELECT pg_drop_replication_slot('test_slot');"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we manually advance this slot's LSN to the
+# latest position.
+##################################################
+
+# Create some data on the primary
+$primary_row_count = 10;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# The subscription that's up and running and is enabled for failover doesn't
+# get the data from primary and keeps waiting for the standby specified in
+# standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary as long as standby1 restart_lsn has not been updated"
+);
+
+# Advance the lsn of the standby slot manually
+$primary->safe_psql('postgres',
+	"SELECT * FROM pg_replication_slot_advance('sb1_slot', pg_current_wal_lsn());"
+);
+
+# Now that the standby lsn has advanced, primary must send the decoded
+# changes to the subscription
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1's restart_lsn has been updated"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we remove the slot from standby_slot_names.
+##################################################
+
+# Create some data on the primary
+$primary_row_count = 20;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(11,20);");
+
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 10 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data as the sb1_slot doesn't catch up");
+
+# Remove the standby from the standby_slot_names list and reload the
+# configuration
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', "''");
+$primary->psql('postgres', "SELECT pg_reload_conf()");
+
+# Now that the standby lsn has advanced, the primary must send the decoded
+# changes to the subscription.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 is removed from the standby_slot_names list"
+);
+
+# Put the standby back on the primary_slot_name for the rest of the tests
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', 'sb1_slot');
+$primary->restart();
+
+##################################################
+# Test that when a subscription with failover enabled is created, it will alter
+# the failover property of the corresponding slot on the publisher.
+##################################################
+
+# Create a slot on the publisher with failover disabled
+$primary->safe_psql('postgres',
+	"SELECT 'init' FROM pg_create_logical_replication_slot('lsub3_slot', 'pgoutput', false, false, false);"
+);
+
+# Confirm that the failover flag on the slot is turned off
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"f",
+	'logical slot has failover false on the primary');
+
+# Create another subscription (using the same slot created above) that enables
+# failover.
+$subscriber1->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub3 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub3_slot, copy_data=false, failover = true, create_slot = false);"
+);
+
+# Confirm that the failover flag on the slot has now been turned on
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"t",
+	'logical slot has failover true on the primary');
+
+$subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ba41149b88..199863c6b5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3870,6 +3871,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.30.0.windows.2



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-12-11 08:17                                             ` Dilip Kumar <[email protected]>
  2023-12-11 08:50                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Dilip Kumar @ 2023-12-11 08:17 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; +Cc: shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Fri, Dec 8, 2023 at 2:36 PM Amit Kapila <[email protected]> wrote:
>
> On Wed, Dec 6, 2023 at 4:53 PM shveta malik <[email protected]> wrote:
> >
> > PFA v43, changes are:
> >
>
> I wanted to discuss 0003 patch about cascading standby's. It is not
> clear to me whether we want to allow physical standbys to further wait
> for cascading standby to sync their slots. If we allow such a feature
> one may expect even primary to wait for all the cascading standby's
> because otherwise still logical subscriber can be ahead of one of the
> cascading standby. I feel even if we want to allow such a behaviour we
> can do it later once the main feature is committed. I think it would
> be good to just allow logical walsenders on primary to wait for
> physical standbys represented by GUC 'standby_slot_names'. If we agree
> on that then it would be good to prohibit setting this GUC on standby
> or at least it should be a no-op even if this GUC should be set on
> physical standby.
>
> Thoughts?

IMHO, why not keep the behavior consistent across primary and standby?
 I mean if it doesn't require a lot of new code/design addition then
it should be the user's responsibility.  I mean if the user has set
'standby_slot_names' on standby then let standby also wait for
cascading standby to sync their slots?  Is there any issue with that
behavior?

-- 
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 08:17                                             ` Re: Synchronizing slots from primary to standby Dilip Kumar <[email protected]>
@ 2023-12-11 08:50                                               ` shveta malik <[email protected]>
  2023-12-11 09:02                                                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-11 11:17                                                 ` Re: Synchronizing slots from primary to standby Dilip Kumar <[email protected]>
  0 siblings, 2 replies; 113+ messages in thread

From: shveta malik @ 2023-12-11 08:50 UTC (permalink / raw)
  To: Dilip Kumar <[email protected]>; +Cc: Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Mon, Dec 11, 2023 at 1:47 PM Dilip Kumar <[email protected]> wrote:
>
> On Fri, Dec 8, 2023 at 2:36 PM Amit Kapila <[email protected]> wrote:
> >
> > On Wed, Dec 6, 2023 at 4:53 PM shveta malik <[email protected]> wrote:
> > >
> > > PFA v43, changes are:
> > >
> >
> > I wanted to discuss 0003 patch about cascading standby's. It is not
> > clear to me whether we want to allow physical standbys to further wait
> > for cascading standby to sync their slots. If we allow such a feature
> > one may expect even primary to wait for all the cascading standby's
> > because otherwise still logical subscriber can be ahead of one of the
> > cascading standby. I feel even if we want to allow such a behaviour we
> > can do it later once the main feature is committed. I think it would
> > be good to just allow logical walsenders on primary to wait for
> > physical standbys represented by GUC 'standby_slot_names'. If we agree
> > on that then it would be good to prohibit setting this GUC on standby
> > or at least it should be a no-op even if this GUC should be set on
> > physical standby.
> >
> > Thoughts?
>
> IMHO, why not keep the behavior consistent across primary and standby?
>  I mean if it doesn't require a lot of new code/design addition then
> it should be the user's responsibility.  I mean if the user has set
> 'standby_slot_names' on standby then let standby also wait for
> cascading standby to sync their slots?  Is there any issue with that
> behavior?
>

Without waiting for cascading standby on primary, it won't be helpful
to just wait on standby.

Currently logical walsenders on primary waits for physical standbys to
take changes before they update their own logical slots. But they wait
only for their immediate standbys and not for cascading standbys.
Although, on first standby, we do have logic where slot-sync workers
wait for cascading standbys before they update their own slots (synced
ones, see patch3). But this does not guarantee that logical
subscribers on primary will never be ahead of the cascading standbys.
Let us consider this timeline:

t1: logical walsender on primary waiting for standby1 (first standby).
t2: physical walsender on standby1 is stuck and thus there is delay in
sending these changes to standby2 (cascading standby).
t3: standby1 has taken changes and sends confirmation to primary.
t4: logical walsender on primary receives confirmation from standby1
and updates slot, logical subscribers of primary also receives the
changes.
t5: standby2 has not received changes yet as physical walsender on
standby1 is still stuck, slotsync worker still waiting for standby2
(cascading) before it updates its own slots (synced ones).
t6: standby2 is promoted to become primary.

Now we are in a state wherein primary, logical subscriber and first
standby has some changes but cascading standby does not. And logical
slots on primary were updated w/o confirming if cascading standby has
taken changes or not. This is a problem and we do not have a simple
solution for this yet.

thanks
Shveta






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 08:17                                             ` Re: Synchronizing slots from primary to standby Dilip Kumar <[email protected]>
  2023-12-11 08:50                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-11 09:02                                                 ` shveta malik <[email protected]>
  2023-12-13 10:22                                                   ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-11 09:02 UTC (permalink / raw)
  To: Dilip Kumar <[email protected]>; Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; +Cc: Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Mon, Dec 11, 2023 at 2:20 PM shveta malik <[email protected]> wrote:
>
> On Mon, Dec 11, 2023 at 1:47 PM Dilip Kumar <[email protected]> wrote:
> >
> > On Fri, Dec 8, 2023 at 2:36 PM Amit Kapila <[email protected]> wrote:
> > >
> > > On Wed, Dec 6, 2023 at 4:53 PM shveta malik <[email protected]> wrote:
> > > >
> > > > PFA v43, changes are:
> > > >
> > >
> > > I wanted to discuss 0003 patch about cascading standby's. It is not
> > > clear to me whether we want to allow physical standbys to further wait
> > > for cascading standby to sync their slots. If we allow such a feature
> > > one may expect even primary to wait for all the cascading standby's
> > > because otherwise still logical subscriber can be ahead of one of the
> > > cascading standby. I feel even if we want to allow such a behaviour we
> > > can do it later once the main feature is committed. I think it would
> > > be good to just allow logical walsenders on primary to wait for
> > > physical standbys represented by GUC 'standby_slot_names'. If we agree
> > > on that then it would be good to prohibit setting this GUC on standby
> > > or at least it should be a no-op even if this GUC should be set on
> > > physical standby.
> > >
> > > Thoughts?
> >
> > IMHO, why not keep the behavior consistent across primary and standby?
> >  I mean if it doesn't require a lot of new code/design addition then
> > it should be the user's responsibility.  I mean if the user has set
> > 'standby_slot_names' on standby then let standby also wait for
> > cascading standby to sync their slots?  Is there any issue with that
> > behavior?
> >
>
> Without waiting for cascading standby on primary, it won't be helpful
> to just wait on standby.
>
> Currently logical walsenders on primary waits for physical standbys to
> take changes before they update their own logical slots. But they wait
> only for their immediate standbys and not for cascading standbys.
> Although, on first standby, we do have logic where slot-sync workers
> wait for cascading standbys before they update their own slots (synced
> ones, see patch3). But this does not guarantee that logical
> subscribers on primary will never be ahead of the cascading standbys.
> Let us consider this timeline:
>
> t1: logical walsender on primary waiting for standby1 (first standby).
> t2: physical walsender on standby1 is stuck and thus there is delay in
> sending these changes to standby2 (cascading standby).
> t3: standby1 has taken changes and sends confirmation to primary.
> t4: logical walsender on primary receives confirmation from standby1
> and updates slot, logical subscribers of primary also receives the
> changes.
> t5: standby2 has not received changes yet as physical walsender on
> standby1 is still stuck, slotsync worker still waiting for standby2
> (cascading) before it updates its own slots (synced ones).
> t6: standby2 is promoted to become primary.
>
> Now we are in a state wherein primary, logical subscriber and first
> standby has some changes but cascading standby does not. And logical
> slots on primary were updated w/o confirming if cascading standby has
> taken changes or not. This is a problem and we do not have a simple
> solution for this yet.
>
> thanks
> Shveta


PFA v45, changes in patch002:

--Addressed comments in [1] and [2]
--Added holistic test case for patch02. Thanks Nisha for the test
implementation.

[1]: https://www.postgresql.org/message-id/CAHut%2BPuuqEpDse5msENsVuK3rjTRN-QGS67rRCGVv%2BzcT-f0GA%40mail...
[2]: https://www.postgresql.org/message-id/CAA4eK1KbhdjKqui%3Dfr4Ny2TwGAFU9WLWTdypN%2BWG0WEfnBR%3D4w%40ma...

thanks
Shveta


Attachments:

  [application/octet-stream] v45-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch (145.9K, ../../CAJpy0uBEWO_8vxJFss0CaAw28QgQtKHh_bqs9iZS3YoyOe4Wyg@mail.gmail.com/2-v45-0001-Allow-logical-walsenders-to-wait-for-the-physica.patch)
  download | inline diff:
From e9c7da62d32bdb0dd62946150d318b97021ed7eb Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Wed, 6 Dec 2023 15:52:16 +0530
Subject: [PATCH v45 1/3] Allow logical walsenders to wait for the physical
 standbys

A new property 'failover' is added at the slot level. This is persistent
information to indicate that this logical slot is enabled to be synced to
the physical standbys so that logical replication can be resumed after
failover. It is always false for physical slots.

Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API.

Ex1:
CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

Ex2: (failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

A new replication command called ALTER_REPLICATION_SLOT and a
corresponding walreceiver API function named walrcv_alter_slot have been
implemented. They allow subscribers or users to modify the failover
property of a replication slot on the publisher.

Altering the failover option of the subscription is currently not
permitted. However, this restriction may be lifted in future versions.

The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

A new GUC standby_slot_names has been added. It is the list of
physical replication slots that logical replication with failover
enabled waits for. The intent of this wait is that no logical
replication subscriptions (with failover=true) should get
ahead of physical replication standbys (corresponding to the
physical slots in standby_slot_names).
---
 contrib/test_decoding/expected/slot.out       |  58 +++
 contrib/test_decoding/sql/slot.sql            |  13 +
 doc/src/sgml/catalogs.sgml                    |  12 +
 doc/src/sgml/config.sgml                      |  16 +
 doc/src/sgml/func.sgml                        |  11 +-
 doc/src/sgml/protocol.sgml                    |  51 +++
 doc/src/sgml/ref/alter_subscription.sgml      |  20 +-
 doc/src/sgml/ref/create_subscription.sgml     |  23 ++
 doc/src/sgml/system-views.sgml                |  11 +
 src/backend/catalog/pg_subscription.c         |   1 +
 src/backend/catalog/system_functions.sql      |   1 +
 src/backend/catalog/system_views.sql          |   6 +-
 src/backend/commands/subscriptioncmds.c       | 110 +++++-
 .../libpqwalreceiver/libpqwalreceiver.c       |  38 +-
 .../replication/logical/logicalfuncs.c        |  13 +
 src/backend/replication/logical/tablesync.c   |  53 ++-
 src/backend/replication/logical/worker.c      |  67 +++-
 src/backend/replication/repl_gram.y           |  18 +-
 src/backend/replication/repl_scanner.l        |   2 +
 src/backend/replication/slot.c                | 182 ++++++++-
 src/backend/replication/slotfuncs.c           |  25 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/replication/walsender.c           | 344 +++++++++++++++++-
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/misc/guc_tables.c           |  14 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/bin/pg_dump/pg_dump.c                     |  20 +-
 src/bin/pg_dump/pg_dump.h                     |   1 +
 src/bin/pg_upgrade/info.c                     |   5 +-
 src/bin/pg_upgrade/pg_upgrade.c               |   6 +-
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/bin/pg_upgrade/t/003_logical_slots.pl     |   6 +-
 src/bin/psql/describe.c                       |   8 +-
 src/bin/psql/tab-complete.c                   |   2 +-
 src/include/catalog/pg_proc.dat               |  14 +-
 src/include/catalog/pg_subscription.h         |  11 +
 src/include/nodes/replnodes.h                 |  12 +
 src/include/replication/slot.h                |  12 +-
 src/include/replication/walreceiver.h         |  18 +-
 src/include/replication/walsender.h           |   4 +
 src/include/replication/walsender_private.h   |   7 +
 src/include/replication/worker_internal.h     |   3 +-
 src/include/utils/guc_hooks.h                 |   3 +
 src/test/recovery/meson.build                 |   1 +
 src/test/recovery/t/006_logical_decoding.pl   |   3 +-
 .../t/050_standby_failover_slots_sync.pl      | 298 +++++++++++++++
 src/test/regress/expected/rules.out           |   5 +-
 src/test/regress/expected/subscription.out    | 165 +++++----
 src/test/regress/sql/subscription.sql         |   8 +
 src/tools/pgindent/typedefs.list              |   2 +
 50 files changed, 1540 insertions(+), 170 deletions(-)
 create mode 100644 src/test/recovery/t/050_standby_failover_slots_sync.pl

diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
index 63a9940f73..261d8886d3 100644
--- a/contrib/test_decoding/expected/slot.out
+++ b/contrib/test_decoding/expected/slot.out
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
index 1aa27c5667..45aeae7fd5 100644
--- a/contrib/test_decoding/sql/slot.sql
+++ b/contrib/test_decoding/sql/slot.sql
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ec7391ec5..e666730c64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7990,6 +7990,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>subfailoverstate</structfield> <type>char</type>
+      </para>
+      <para>
+       State codes for failover mode:
+       <literal>d</literal> = disabled,
+       <literal>p</literal> = pending enablement,
+       <literal>e</literal> = enabled
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>subconninfo</structfield> <type>text</type>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 94d1eb2b81..30d9b53e03 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4360,6 +4360,22 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-standby-slot-names" xreflabel="standby_slot_names">
+      <term><varname>standby_slot_names</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>standby_slot_names</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        List of physical replication slots that logical replication slots with
+        failover enabled waits for. If a logical replication connection is
+        meant to switch to a physical standby after the standby is promoted,
+        the physical replication slot for the standby should be listed here.
+       </para>
+      </listitem>
+     </varlistentry>
+
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..90f1f19018 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27541,7 +27541,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27556,8 +27556,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        physical standbys so that logical replication can be resumed
+        after failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index af3f016f74..bb926ab149 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2060,6 +2060,16 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist>
 
       <para>
@@ -2124,6 +2134,47 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </listitem>
     </varlistentry>
 
+    <varlistentry id="protocol-replication-alter-replication-slot" xreflabel="ALTER_REPLICATION_SLOT">
+     <term><literal>ALTER_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable> ( <replaceable class="parameter">option</replaceable> [, ...] )
+      <indexterm><primary>ALTER_REPLICATION_SLOT</primary></indexterm>
+     </term>
+     <listitem>
+      <para>
+       Change the definition of a replication slot.
+       See <xref linkend="streaming-replication-slots"/> for more about
+       replication slots. This command is currently only supported for logical
+       replication slots.
+      </para>
+
+      <variablelist>
+       <varlistentry>
+        <term><replaceable class="parameter">slot_name</replaceable></term>
+        <listitem>
+         <para>
+          The name of the slot to alter. Must be a valid replication slot
+          name (see <xref linkend="streaming-replication-slots-manipulation"/>).
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+      <para>The following options are supported:</para>
+
+      <variablelist>
+       <varlistentry>
+        <term><literal>FAILOVER { 'true' | 'false' }</literal></term>
+        <listitem>
+         <para>
+          If true, the slot is enabled to be synced to the physical
+          standbys so that logical replication can be resumed after failover.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="protocol-replication-read-replication-slot">
      <term><literal>READ_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</replaceable>
       <indexterm><primary>READ_REPLICATION_SLOT</primary></indexterm>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 6d36ff0dc9..481e397bad 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -73,11 +73,14 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
 
    These commands also cannot be executed when the subscription has
    <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-   commit enabled, unless
+   commit enabled or
+   <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+   enabled, unless
    <link linkend="sql-createsubscription-params-with-copy-data"><literal>copy_data</literal></link>
    is <literal>false</literal>. See column <structfield>subtwophasestate</structfield>
-   of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
-   to know the actual two-phase state.
+   and <structfield>subfailoverstate</structfield> of
+   <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+   to know the actual state.
   </para>
  </refsect1>
 
@@ -230,6 +233,17 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
       <link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>.
       Only a superuser can set <literal>password_required = false</literal>.
      </para>
+
+     <para>
+      When altering the
+      <link linkend="sql-createsubscription-params-with-slot-name"><literal>slot_name</literal></link>,
+      the <literal>failover</literal> property of the new slot may differ from the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter specified in the subscription. When creating the slot,
+      ensure the slot failover property matches the
+      <link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>
+      parameter value of the subscription.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index f1c20b3a46..fa6cd1c43f 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -399,6 +399,29 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry id="sql-createsubscription-params-with-failover">
+        <term><literal>failover</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the replication slot associated with the subscription
+          is enabled to be synced to the physical standbys so that logical
+          replication can be resumed from the new primary after failover.
+          The default is <literal>false</literal>.
+         </para>
+
+         <para>
+          The implementation of failover requires that replication
+          has successfully finished the initial table synchronization
+          phase. So even when <literal>failover</literal> is enabled for a
+          subscription, the internal failover state remains
+          temporarily <quote>pending</quote> until the initialization phase
+          completes. See column <structfield>subfailoverstate</structfield>
+          of <link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link>
+          to know the actual failover state.
+         </para>
+        </listitem>
+       </varlistentry>
       </variablelist></para>
 
     </listitem>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 0ef1745631..1dc695fd3a 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2532,6 +2532,17 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        invalidated). Always NULL for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this logical slot is enabled to be synced to the physical
+       standbys so that logical replication can be resumed from the new primary
+       after failover. Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d6a978f136..18512955ad 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -73,6 +73,7 @@ GetSubscription(Oid subid, bool missing_ok)
 	sub->disableonerr = subform->subdisableonerr;
 	sub->passwordrequired = subform->subpasswordrequired;
 	sub->runasowner = subform->subrunasowner;
+	sub->failoverstate = subform->subfailoverstate;
 
 	/* Get conninfo */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 4206752881..4db796aa0b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 11d18ed9dd..63038f87f7 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflicting
+            L.conflicting,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
@@ -1354,7 +1355,8 @@ REVOKE ALL ON pg_subscription FROM public;
 GRANT SELECT (oid, subdbid, subskiplsn, subname, subowner, subenabled,
               subbinary, substream, subtwophasestate, subdisableonerr,
 			  subpasswordrequired, subrunasowner,
-              subslotname, subsynccommit, subpublications, suborigin)
+              subslotname, subsynccommit, subpublications, suborigin,
+              subfailoverstate)
     ON pg_subscription TO public;
 
 CREATE VIEW pg_stat_subscription_stats AS
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index edc82c11be..67826b89c2 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -71,6 +71,7 @@
 #define SUBOPT_RUN_AS_OWNER			0x00001000
 #define SUBOPT_LSN					0x00002000
 #define SUBOPT_ORIGIN				0x00004000
+#define SUBOPT_FAILOVER				0x00008000
 
 /* check if the 'val' has 'bits' set */
 #define IsSet(val, bits)  (((val) & (bits)) == (bits))
@@ -96,6 +97,7 @@ typedef struct SubOpts
 	bool		passwordrequired;
 	bool		runasowner;
 	char	   *origin;
+	bool		failover;
 	XLogRecPtr	lsn;
 } SubOpts;
 
@@ -157,6 +159,8 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 		opts->runasowner = false;
 	if (IsSet(supported_opts, SUBOPT_ORIGIN))
 		opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
+	if (IsSet(supported_opts, SUBOPT_FAILOVER))
+		opts->failover = false;
 
 	/* Parse options */
 	foreach(lc, stmt_options)
@@ -326,6 +330,15 @@ parse_subscription_options(ParseState *pstate, List *stmt_options,
 						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						errmsg("unrecognized origin value: \"%s\"", opts->origin));
 		}
+		else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
+				 strcmp(defel->defname, "failover") == 0)
+		{
+			if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
+				errorConflictingDefElem(defel, pstate);
+
+			opts->specified_opts |= SUBOPT_FAILOVER;
+			opts->failover = defGetBoolean(defel);
+		}
 		else if (IsSet(supported_opts, SUBOPT_LSN) &&
 				 strcmp(defel->defname, "lsn") == 0)
 		{
@@ -591,7 +604,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					  SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
 					  SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
 					  SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
-					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN);
+					  SUBOPT_RUN_AS_OWNER | SUBOPT_ORIGIN |
+					  SUBOPT_FAILOVER);
 	parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
 
 	/*
@@ -710,6 +724,10 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 		publicationListToArray(publications);
 	values[Anum_pg_subscription_suborigin - 1] =
 		CStringGetTextDatum(opts.origin);
+	values[Anum_pg_subscription_subfailoverstate - 1] =
+		CharGetDatum(opts.failover ?
+					 LOGICALREP_FAILOVER_STATE_PENDING :
+					 LOGICALREP_FAILOVER_STATE_DISABLED);
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -746,6 +764,8 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 
 		PG_TRY();
 		{
+			bool		failover_enabled = false;
+
 			check_publications(wrconn, publications);
 			check_publications_origin(wrconn, publications, opts.copy_data,
 									  opts.origin, NULL, 0, stmt->subname);
@@ -776,6 +796,19 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										InvalidXLogRecPtr);
 			}
 
+			/*
+			 * Even if failover is set, don't create the slot with failover
+			 * enabled. Will enable it once all the tables are synced and
+			 * ready. The intention is that if failover happens at the time of
+			 * table-sync, user should re-launch the subscription instead of
+			 * relying on main slot (if synced) with no table-sync data
+			 * present. When the subscription has no tables, leave failover as
+			 * false to allow ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
+			 * work.
+			 */
+			if (opts.failover && !opts.copy_data && tables != NIL)
+				failover_enabled = true;
+
 			/*
 			 * If requested, create permanent slot for the subscription. We
 			 * won't use the initial snapshot for anything, so no need to
@@ -807,15 +840,34 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 					twophase_enabled = true;
 
 				walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
-								   CRS_NOEXPORT_SNAPSHOT, NULL);
-
-				if (twophase_enabled)
-					UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
+								   failover_enabled, CRS_NOEXPORT_SNAPSHOT, NULL);
 
+				/* Update twophase and/or failover state */
+				EnableTwoPhaseFailoverTriState(subid, twophase_enabled,
+											   failover_enabled);
 				ereport(NOTICE,
 						(errmsg("created replication slot \"%s\" on publisher",
 								opts.slot_name)));
 			}
+
+			/*
+			 * If the slot_name is specified without the create_slot option,
+			 * it is possible that the user intends to use an existing slot on
+			 * the publisher, so here we alter the failover property of the
+			 * slot to match the failover value in subscription.
+			 *
+			 * We do not need to change the failover to false if the server
+			 * does not support failover (e.g. pre-PG17)
+			 */
+			else if (opts.slot_name &&
+					 (failover_enabled || walrcv_server_version(wrconn) >= 170000))
+			{
+				walrcv_alter_slot(wrconn, opts.slot_name, failover_enabled);
+				ereport(NOTICE,
+						(errmsg("changed the failover state of replication slot \"%s\" on publisher to %s",
+								opts.slot_name,
+								failover_enabled ? "true" : "false")));
+			}
 		}
 		PG_FINALLY();
 		{
@@ -1279,13 +1331,22 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 								 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 					PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
@@ -1334,13 +1395,26 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 										 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
 
 					/*
-					 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
-					 * not allowed.
+					 * See ALTER_SUBSCRIPTION_REFRESH for details why
+					 * copy_data is not allowed when twophase or failover is
+					 * enabled.
 					 */
 					if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "two_phase"),
+						/* translator: %s is an SQL ALTER command */
+								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
+										 isadd ?
+										 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
+										 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
+
+					if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+						ereport(ERROR,
+								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+						/* translator: %s is a subscription option */
+								 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when %s is enabled", "failover"),
 						/* translator: %s is an SQL ALTER command */
 								 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
 										 isadd ?
@@ -1389,7 +1463,19 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
 				if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
 					ereport(ERROR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
-							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "two_phase"),
+							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
+
+				/*
+				 * See comments above for twophasestate, same holds true for
+				 * 'failover'
+				 */
+				if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+					/* translator: %s is a subscription option */
+							 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when %s is enabled", "failover"),
 							 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
 
 				PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 4152d1ddbc..ff65fdb4d9 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -74,8 +74,11 @@ static char *libpqrcv_create_slot(WalReceiverConn *conn,
 								  const char *slotname,
 								  bool temporary,
 								  bool two_phase,
+								  bool failover,
 								  CRSSnapshotAction snapshot_action,
 								  XLogRecPtr *lsn);
+static void libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+								bool failover);
 static pid_t libpqrcv_get_backend_pid(WalReceiverConn *conn);
 static WalRcvExecResult *libpqrcv_exec(WalReceiverConn *conn,
 									   const char *query,
@@ -96,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_receive = libpqrcv_receive,
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
+	.walrcv_alter_slot = libpqrcv_alter_slot,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -888,8 +892,8 @@ libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
  */
 static char *
 libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
-					 bool temporary, bool two_phase, CRSSnapshotAction snapshot_action,
-					 XLogRecPtr *lsn)
+					 bool temporary, bool two_phase, bool failover,
+					 CRSSnapshotAction snapshot_action, XLogRecPtr *lsn)
 {
 	PGresult   *res;
 	StringInfoData cmd;
@@ -918,7 +922,8 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 			else
 				appendStringInfoChar(&cmd, ' ');
 		}
-
+		if (failover)
+			appendStringInfoString(&cmd, "FAILOVER, ");
 		if (use_new_options_syntax)
 		{
 			switch (snapshot_action)
@@ -987,6 +992,33 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
 	return snapshot;
 }
 
+/*
+ * Change the definition of the replication slot.
+ */
+static void
+libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
+					bool failover)
+{
+	StringInfoData cmd;
+	PGresult   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "ALTER_REPLICATION_SLOT %s ( FAILOVER %s )",
+					 quote_identifier(slotname),
+					 failover ? "true" : "false");
+
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("could not alter replication slot \"%s\" on publisher: %s",
+						slotname, pchomp(PQerrorMessage(conn->streamConn)))));
+
+	PQclear(res);
+}
+
 /*
  * Return PID of remote backend process.
  */
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 1067aca08f..a36366e117 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -30,6 +30,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/message.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -109,6 +110,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	MemoryContext per_query_ctx;
 	MemoryContext oldcontext;
 	XLogRecPtr	end_of_wal;
+	XLogRecPtr	wait_for_wal_lsn;
 	LogicalDecodingContext *ctx;
 	ResourceOwner old_resowner = CurrentResourceOwner;
 	ArrayType  *arr;
@@ -228,6 +230,17 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 							NameStr(MyReplicationSlot->data.plugin),
 							format_procedure(fcinfo->flinfo->fn_oid))));
 
+		if (XLogRecPtrIsInvalid(upto_lsn))
+			wait_for_wal_lsn = end_of_wal;
+		else
+			wait_for_wal_lsn = Min(upto_lsn, end_of_wal);
+
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to wait_for_wal_lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(wait_for_wal_lsn);
+
 		ctx->output_writer_private = p;
 
 		/*
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 4d056c16c8..7b6170fe55 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -624,15 +624,28 @@ process_syncing_tables_for_apply(XLogRecPtr current_lsn)
 		 * Note: If the subscription has no tables then leave the state as
 		 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 		 * work.
+		 *
+		 * Same goes for 'failover'. Enable it only if subscription has tables
+		 * and all the tablesyncs have reached READY state.
 		 */
-		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+		if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ||
+			MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
 		{
 			CommandCounterIncrement();	/* make updates visible */
 			if (AllTablesyncsReady())
 			{
-				ereport(LOG,
-						(errmsg("logical replication apply worker for subscription \"%s\" will restart so that two_phase can be enabled",
-								MySubscription->name)));
+				if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "two_phase")));
+
+				if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+					ereport(LOG,
+					/* translator: %s is a subscription option */
+							(errmsg("logical replication apply worker for subscription \"%s\" will restart so that %s can be enabled",
+									MySubscription->name, "failover")));
+
 				should_exit = true;
 			}
 		}
@@ -1430,7 +1443,8 @@ LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
 	 */
 	walrcv_create_slot(LogRepWorkerWalRcvConn,
 					   slotname, false /* permanent */ , false /* two_phase */ ,
-					   CRS_USE_SNAPSHOT, origin_startpos);
+					   false /* failover */ , CRS_USE_SNAPSHOT,
+					   origin_startpos);
 
 	/*
 	 * Setup replication origin tracking. The purpose of doing this before the
@@ -1732,10 +1746,12 @@ AllTablesyncsReady(void)
 }
 
 /*
- * Update the two_phase state of the specified subscription in pg_subscription.
+ * Update the twophase and/or failover state of the specified subscription
+ * in pg_subscription.
  */
 void
-UpdateTwoPhaseState(Oid suboid, char new_state)
+EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+							   bool enable_failover)
 {
 	Relation	rel;
 	HeapTuple	tup;
@@ -1743,9 +1759,8 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	bool		replaces[Natts_pg_subscription];
 	Datum		values[Natts_pg_subscription];
 
-	Assert(new_state == LOGICALREP_TWOPHASE_STATE_DISABLED ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_PENDING ||
-		   new_state == LOGICALREP_TWOPHASE_STATE_ENABLED);
+	if (!enable_twophase && !enable_failover)
+		return;
 
 	rel = table_open(SubscriptionRelationId, RowExclusiveLock);
 	tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(suboid));
@@ -1759,9 +1774,21 @@ UpdateTwoPhaseState(Oid suboid, char new_state)
 	memset(nulls, false, sizeof(nulls));
 	memset(replaces, false, sizeof(replaces));
 
-	/* And update/set two_phase state */
-	values[Anum_pg_subscription_subtwophasestate - 1] = CharGetDatum(new_state);
-	replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	/* Update/set two_phase state if asked by the caller */
+	if (enable_twophase)
+	{
+		values[Anum_pg_subscription_subtwophasestate - 1] =
+			CharGetDatum(LOGICALREP_TWOPHASE_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
+	}
+
+	/* Update/set failover state if asked by the caller */
+	if (enable_failover)
+	{
+		values[Anum_pg_subscription_subfailoverstate - 1] =
+			CharGetDatum(LOGICALREP_FAILOVER_STATE_ENABLED);
+		replaces[Anum_pg_subscription_subfailoverstate - 1] = true;
+	}
 
 	tup = heap_modify_tuple(tup, RelationGetDescr(rel),
 							values, nulls, replaces);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 21abf34ef7..e46a1955e8 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -132,6 +132,33 @@
  * avoid such deadlocks, we generate a unique GID (consisting of the
  * subscription oid and the xid of the prepared transaction) for each prepare
  * transaction on the subscriber.
+ *
+ * FAILOVER
+ * ----------------------
+ * The logical slot on the primary can be synced to the standby by specifying
+ * failover = true when creating the subscription. Enabling failover allows us
+ * to smoothly transition to the promoted standby, ensuring that we can
+ * subscribe to the new primary without losing any data.
+ *
+ * However, we do not enable failover for slots created by the table sync
+ * worker.
+ *
+ * Additionally, failover is not enabled for the main slot if the table sync is
+ * in progress. This is because if a failover occurs while the table sync
+ * worker has reached a certain state (SUBREL_STATE_FINISHEDCOPY or
+ * SUBREL_STATE_DATASYNC), replication will not be able to continue from the
+ * new primary node.
+ *
+ * As a result, we enable the failover option for the main slot only after the
+ * initial sync is complete. The failover option is implemented as a tri-state
+ * with values DISABLED, PENDING, and ENABLED. The state transition process
+ * between these values is the same as the two_phase option (see TWO_PHASE
+ * TRANSACTIONS for details).
+ *
+ * During the startup of the apply worker, it checks if all table syncs are in
+ * the READY state for a failover tri-state of PENDING. If so, it alters the
+ * main slot's failover property to true and updates the tri-state value from
+ * PENDING to ENABLED.
  *-------------------------------------------------------------------------
  */
 
@@ -3947,6 +3974,7 @@ maybe_reread_subscription(void)
 		newsub->passwordrequired != MySubscription->passwordrequired ||
 		strcmp(newsub->origin, MySubscription->origin) != 0 ||
 		newsub->owner != MySubscription->owner ||
+		newsub->failoverstate != MySubscription->failoverstate ||
 		!equal(newsub->publications, MySubscription->publications))
 	{
 		if (am_parallel_apply_worker())
@@ -4482,6 +4510,8 @@ run_apply_worker()
 	TimeLineID	startpointTLI;
 	char	   *err;
 	bool		must_use_password;
+	bool		twophase_pending;
+	bool		failover_pending;
 
 	slotname = MySubscription->slotname;
 
@@ -4538,17 +4568,38 @@ run_apply_worker()
 	 * Note: If the subscription has no tables then leave the state as
 	 * PENDING, which allows ALTER SUBSCRIPTION ... REFRESH PUBLICATION to
 	 * work.
+	 *
+	 * Same goes for 'failover'. It is enabled only if subscription has tables
+	 * and all the tablesyncs have reached READY state, until then it remains
+	 * as PENDING.
 	 */
-	if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING &&
-		AllTablesyncsReady())
+	twophase_pending =
+		(MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING);
+	failover_pending =
+		(MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING);
+
+	if ((twophase_pending || failover_pending) && AllTablesyncsReady())
 	{
 		/* Start streaming with two_phase enabled */
-		options.proto.logical.twophase = true;
+		if (twophase_pending)
+			options.proto.logical.twophase = true;
+
+		if (failover_pending)
+			walrcv_alter_slot(LogRepWorkerWalRcvConn, slotname, true);
+
 		walrcv_startstreaming(LogRepWorkerWalRcvConn, &options);
 
 		StartTransactionCommand();
-		UpdateTwoPhaseState(MySubscription->oid, LOGICALREP_TWOPHASE_STATE_ENABLED);
-		MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		/* Update twophase and/or failover */
+		EnableTwoPhaseFailoverTriState(MySubscription->oid, twophase_pending,
+									   failover_pending);
+		if (twophase_pending)
+			MySubscription->twophasestate = LOGICALREP_TWOPHASE_STATE_ENABLED;
+
+		if (failover_pending)
+			MySubscription->failoverstate = LOGICALREP_FAILOVER_STATE_ENABLED;
+
 		CommitTransactionCommand();
 	}
 	else
@@ -4557,11 +4608,15 @@ run_apply_worker()
 	}
 
 	ereport(DEBUG1,
-			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s",
+			(errmsg_internal("logical replication apply worker for subscription \"%s\" two_phase is %s and failover is %s",
 							 MySubscription->name,
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_DISABLED ? "DISABLED" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING ? "PENDING" :
 							 MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED ? "ENABLED" :
+							 "?",
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_DISABLED ? "DISABLED" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING ? "PENDING" :
+							 MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED ? "ENABLED" :
 							 "?")));
 
 	/* Run the main loop. */
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index 0c874e33cf..b706046811 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -64,6 +64,7 @@ Node *replication_parse_result;
 %token K_START_REPLICATION
 %token K_CREATE_REPLICATION_SLOT
 %token K_DROP_REPLICATION_SLOT
+%token K_ALTER_REPLICATION_SLOT
 %token K_TIMELINE_HISTORY
 %token K_WAIT
 %token K_TIMELINE
@@ -79,7 +80,8 @@ Node *replication_parse_result;
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
-				create_replication_slot drop_replication_slot identify_system
+				create_replication_slot drop_replication_slot
+				alter_replication_slot identify_system
 				read_replication_slot timeline_history show
 %type <list>	generic_option_list
 %type <defelt>	generic_option
@@ -111,6 +113,7 @@ command:
 			| start_logical_replication
 			| create_replication_slot
 			| drop_replication_slot
+			| alter_replication_slot
 			| read_replication_slot
 			| timeline_history
 			| show
@@ -257,6 +260,18 @@ drop_replication_slot:
 				}
 			;
 
+/* ALTER_REPLICATION_SLOT slot */
+alter_replication_slot:
+			K_ALTER_REPLICATION_SLOT IDENT '(' generic_option_list ')'
+				{
+					AlterReplicationSlotCmd *cmd;
+					cmd = makeNode(AlterReplicationSlotCmd);
+					cmd->slotname = $2;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+			;
+
 /*
  * START_REPLICATION [SLOT slot] [PHYSICAL] %X/%X [TIMELINE %d]
  */
@@ -399,6 +414,7 @@ ident_or_keyword:
 			| K_START_REPLICATION			{ $$ = "start_replication"; }
 			| K_CREATE_REPLICATION_SLOT	{ $$ = "create_replication_slot"; }
 			| K_DROP_REPLICATION_SLOT		{ $$ = "drop_replication_slot"; }
+			| K_ALTER_REPLICATION_SLOT		{ $$ = "alter_replication_slot"; }
 			| K_TIMELINE_HISTORY			{ $$ = "timeline_history"; }
 			| K_WAIT						{ $$ = "wait"; }
 			| K_TIMELINE					{ $$ = "timeline"; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index 1cc7fb858c..0b5ae23195 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -125,6 +125,7 @@ TIMELINE			{ return K_TIMELINE; }
 START_REPLICATION	{ return K_START_REPLICATION; }
 CREATE_REPLICATION_SLOT		{ return K_CREATE_REPLICATION_SLOT; }
 DROP_REPLICATION_SLOT		{ return K_DROP_REPLICATION_SLOT; }
+ALTER_REPLICATION_SLOT		{ return K_ALTER_REPLICATION_SLOT; }
 TIMELINE_HISTORY	{ return K_TIMELINE_HISTORY; }
 PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
@@ -301,6 +302,7 @@ replication_scanner_is_replication_command(void)
 		case K_START_REPLICATION:
 		case K_CREATE_REPLICATION_SLOT:
 		case K_DROP_REPLICATION_SLOT:
+		case K_ALTER_REPLICATION_SLOT:
 		case K_READ_REPLICATION_SLOT:
 		case K_TIMELINE_HISTORY:
 		case K_SHOW:
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 18bc28195b..d5e729b2eb 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,9 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
+#include "utils/guc_hooks.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -90,7 +93,7 @@ typedef struct ReplicationSlotOnDisk
 	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC		0x1051CA1	/* format identifier */
-#define SLOT_VERSION	3		/* version for new files */
+#define SLOT_VERSION	4		/* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -98,10 +101,19 @@ ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
 /* My backend's replication slot in the shared memory array */
 ReplicationSlot *MyReplicationSlot = NULL;
 
-/* GUC variable */
+/* GUC variables */
 int			max_replication_slots = 10; /* the maximum number of replication
 										 * slots */
 
+/*
+ * This GUC lists streaming replication standby server slot names that
+ * logical WAL sender processes will wait for.
+ */
+char	   *standby_slot_names;
+
+/* This is parsed and cached list for raw standby_slot_names. */
+static List *standby_slot_names_list = NIL;
+
 static void ReplicationSlotShmemExit(int code, Datum arg);
 static void ReplicationSlotDropAcquired(void);
 static void ReplicationSlotDropPtr(ReplicationSlot *slot);
@@ -248,10 +260,13 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     during getting changes, if the two_phase option is enabled it can skip
  *     prepare because by that time start decoding point has been moved. So the
  *     user will only get commit prepared.
+ * failover: Allows the slot to be synced to physical standbys so that logical
+ *     replication can be resumed after failover.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-					  ReplicationSlotPersistency persistency, bool two_phase)
+					  ReplicationSlotPersistency persistency,
+					  bool two_phase, bool failover)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -311,6 +326,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.persistency = persistency;
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
+	slot->data.failover = failover;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -679,6 +695,31 @@ ReplicationSlotDrop(const char *name, bool nowait)
 	ReplicationSlotDropAcquired();
 }
 
+/*
+ * Change the definition of the slot identified by the specified name.
+ */
+void
+ReplicationSlotAlter(const char *name, bool failover)
+{
+	Assert(MyReplicationSlot == NULL);
+
+	ReplicationSlotAcquire(name, true);
+
+	if (SlotIsPhysical(MyReplicationSlot))
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot use %s with a physical replication slot",
+					   "ALTER_REPLICATION_SLOT"));
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.failover = failover;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+	ReplicationSlotSave();
+	ReplicationSlotRelease();
+}
+
 /*
  * Permanently drop the currently acquired replication slot.
  */
@@ -2159,3 +2200,138 @@ RestoreSlotFromDisk(const char *name)
 				(errmsg("too many replication slots active before shutdown"),
 				 errhint("Increase max_replication_slots and try again.")));
 }
+
+/*
+ * A helper function to validate slots specified in GUC standby_slot_names.
+ */
+static bool
+validate_standby_slots(char **newval)
+{
+	char	   *rawname;
+	List	   *elemlist;
+	ListCell   *lc;
+	bool		ok = true;
+
+	/* Need a modifiable copy of string */
+	rawname = pstrdup(*newval);
+
+	/* Verify syntax and parse string into list of identifiers */
+	if (!(ok = SplitIdentifierString(rawname, ',', &elemlist)))
+		GUC_check_errdetail("List syntax is invalid.");
+
+	/*
+	 * If there is a syntax error in the name or if the replication slots'
+	 * data is not initialized yet (i.e., we are in the startup process), skip
+	 * the slot verification.
+	 */
+	if (!ok || !ReplicationSlotCtl)
+	{
+		pfree(rawname);
+		list_free(elemlist);
+		return ok;
+	}
+
+	foreach(lc, elemlist)
+	{
+		char	   *name = lfirst(lc);
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (!slot)
+		{
+			GUC_check_errdetail("replication slot \"%s\" does not exist",
+								name);
+			ok = false;
+			break;
+		}
+
+		if (!SlotIsPhysical(slot))
+		{
+			GUC_check_errdetail("\"%s\" is not a physical replication slot",
+								name);
+			ok = false;
+			break;
+		}
+	}
+
+	pfree(rawname);
+	list_free(elemlist);
+	return ok;
+}
+
+/*
+ * GUC check_hook for standby_slot_names
+ */
+bool
+check_standby_slot_names(char **newval, void **extra, GucSource source)
+{
+	if (strcmp(*newval, "") == 0)
+		return true;
+
+	/*
+	 * "*" is not accepted as in that case primary will not be able to know
+	 * for which all standbys to wait for. Even if we have physical-slots
+	 * info, there is no way to confirm whether there is any standby
+	 * configured for the known physical slots.
+	 */
+	if (strcmp(*newval, "*") == 0)
+	{
+		GUC_check_errdetail("\"%s\" is not accepted for standby_slot_names",
+							*newval);
+		return false;
+	}
+
+	/* Now verify if the specified slots really exist and have correct type */
+	if (!validate_standby_slots(newval))
+		return false;
+
+	*extra = guc_strdup(ERROR, *newval);
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for standby_slot_names
+ */
+void
+assign_standby_slot_names(const char *newval, void *extra)
+{
+	List	   *standby_slots;
+	MemoryContext oldcxt;
+	char	   *standby_slot_names_cpy = extra;
+
+	list_free(standby_slot_names_list);
+	standby_slot_names_list = NIL;
+
+	/* No value is specified for standby_slot_names. */
+	if (standby_slot_names_cpy == NULL)
+		return;
+
+	if (!SplitIdentifierString(standby_slot_names_cpy, ',', &standby_slots))
+	{
+		/* This should not happen if GUC checked check_standby_slot_names. */
+		elog(ERROR, "invalid list syntax");
+	}
+
+	/*
+	 * Switch to the same memory context under which GUC variables are
+	 * allocated (GUCMemoryContext).
+	 */
+	oldcxt = MemoryContextSwitchTo(GetMemoryChunkContext(standby_slot_names_cpy));
+	standby_slot_names_list = list_copy(standby_slots);
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Return a copy of standby_slot_names_list if the copy flag is set to true,
+ * otherwise return the original list.
+ */
+List *
+GetStandbySlotList(bool copy)
+{
+	if (copy)
+		return list_copy(standby_slot_names_list);
+	else
+		return standby_slot_names_list;
+}
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..c87f61666d 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -21,6 +21,7 @@
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/pg_lsn.h"
@@ -42,7 +43,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
-						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+						  false);
 
 	if (immediately_reserve)
 	{
@@ -117,6 +119,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
 								bool temporary, bool two_phase,
+								bool failover,
 								XLogRecPtr restart_lsn,
 								bool find_startpoint)
 {
@@ -133,7 +136,8 @@ create_logical_replication_slot(char *name, char *plugin,
 	 * error as well.
 	 */
 	ReplicationSlotCreate(name, true,
-						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+						  failover);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -171,6 +175,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	Name		plugin = PG_GETARG_NAME(1);
 	bool		temporary = PG_GETARG_BOOL(2);
 	bool		two_phase = PG_GETARG_BOOL(3);
+	bool		failover = PG_GETARG_BOOL(4);
 	Datum		result;
 	TupleDesc	tupdesc;
 	HeapTuple	tuple;
@@ -188,6 +193,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 									NameStr(*plugin),
 									temporary,
 									two_phase,
+									failover,
 									InvalidXLogRecPtr,
 									true);
 
@@ -232,7 +238,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -412,6 +418,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				values[i++] = BoolGetDatum(false);
 		}
 
+		values[i++] = BoolGetDatum(slot_contents.data.failover);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -451,6 +459,8 @@ pg_physical_replication_slot_advance(XLogRecPtr moveto)
 		 * crash, but this makes the data consistent after a clean shutdown.
 		 */
 		ReplicationSlotMarkDirty();
+
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	return retlsn;
@@ -491,6 +501,12 @@ pg_logical_replication_slot_advance(XLogRecPtr moveto)
 											   .segment_close = wal_segment_close),
 									NULL, NULL, NULL);
 
+		/*
+		 * Wait for specified streaming replication standby servers (if any)
+		 * to confirm receipt of WAL up to moveto lsn.
+		 */
+		WalSndWaitForStandbyConfirmation(moveto);
+
 		/*
 		 * Start reading at the slot's restart_lsn, which we know to point to
 		 * a valid record.
@@ -679,6 +695,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	XLogRecPtr	src_restart_lsn;
 	bool		src_islogical;
 	bool		temporary;
+	bool		failover;
 	char	   *plugin;
 	Datum		values[2];
 	bool		nulls[2];
@@ -734,6 +751,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 	src_islogical = SlotIsLogical(&first_slot_contents);
 	src_restart_lsn = first_slot_contents.data.restart_lsn;
 	temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+	failover = first_slot_contents.data.failover;
 	plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
 	/* Check type of replication slot */
@@ -773,6 +791,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
 										plugin,
 										temporary,
 										false,
+										failover,
 										src_restart_lsn,
 										false);
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 26ded928a7..ca61a99785 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -387,7 +387,7 @@ WalReceiverMain(void)
 					 "pg_walreceiver_%lld",
 					 (long long int) walrcv_get_backend_pid(wrconn));
 
-			walrcv_create_slot(wrconn, slotname, true, false, 0, NULL);
+			walrcv_create_slot(wrconn, slotname, true, false, false, 0, NULL);
 
 			SpinLockAcquire(&walrcv->mutex);
 			strlcpy(walrcv->slotname, slotname, NAMEDATALEN);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3bc9c82389..b5493fcd69 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -974,12 +974,13 @@ static void
 parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 						   bool *reserve_wal,
 						   CRSSnapshotAction *snapshot_action,
-						   bool *two_phase)
+						   bool *two_phase, bool *failover)
 {
 	ListCell   *lc;
 	bool		snapshot_action_given = false;
 	bool		reserve_wal_given = false;
 	bool		two_phase_given = false;
+	bool		failover_given = false;
 
 	/* Parse options */
 	foreach(lc, cmd->options)
@@ -1029,6 +1030,15 @@ parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
 			two_phase_given = true;
 			*two_phase = defGetBoolean(defel);
 		}
+		else if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given || cmd->kind != REPLICATION_KIND_LOGICAL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
 		else
 			elog(ERROR, "unrecognized option: %s", defel->defname);
 	}
@@ -1045,6 +1055,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	char	   *slot_name;
 	bool		reserve_wal = false;
 	bool		two_phase = false;
+	bool		failover = false;
 	CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
 	DestReceiver *dest;
 	TupOutputState *tstate;
@@ -1054,13 +1065,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	Assert(!MyReplicationSlot);
 
-	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase);
-
+	parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action, &two_phase,
+							   &failover);
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false);
+							  false, false);
 
 		if (reserve_wal)
 		{
@@ -1091,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase);
+							  two_phase, failover);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1246,6 +1257,46 @@ DropReplicationSlot(DropReplicationSlotCmd *cmd)
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
 }
 
+/*
+ * Process extra options given to ALTER_REPLICATION_SLOT.
+ */
+static void
+parseAlterReplSlotOptions(AlterReplicationSlotCmd *cmd, bool *failover)
+{
+	ListCell   *lc;
+	bool		failover_given = false;
+
+	/* Parse options */
+	foreach(lc, cmd->options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "failover") == 0)
+		{
+			if (failover_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			failover_given = true;
+			*failover = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Change the definition of a replication slot.
+ */
+static void
+AlterReplicationSlot(AlterReplicationSlotCmd *cmd)
+{
+	bool		failover = false;
+
+	parseAlterReplSlotOptions(cmd, &failover);
+	ReplicationSlotAlter(cmd->slotname, failover);
+}
+
 /*
  * Load previously initiated logical slot and prepare for sending data (via
  * WalSndLoop).
@@ -1527,27 +1578,243 @@ WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId
 		ProcessPendingWrites();
 }
 
+/*
+ * Wake up the logical walsender processes with failover-enabled slots if the
+ * physical slot of the current walsender is specified in standby_slot_names
+ * GUC.
+ */
+void
+PhysicalWakeupLogicalWalSnd(void)
+{
+	ListCell   *lc;
+	List	   *standby_slots;
+
+	Assert(MyReplicationSlot && SlotIsPhysical(MyReplicationSlot));
+
+	standby_slots = GetStandbySlotList(false);
+
+	foreach(lc, standby_slots)
+	{
+		char	   *name = lfirst(lc);
+
+		if (strcmp(name, NameStr(MyReplicationSlot->data.name)) == 0)
+		{
+			ConditionVariableBroadcast(&WalSndCtl->wal_confirm_rcv_cv);
+			return;
+		}
+	}
+}
+
+/*
+ * Reload the config file and reinitialize the standby slot list if the GUC
+ * standby_slot_names has changed.
+ */
+static void
+WalSndRereadConfigAndReInitSlotList(List **standby_slots)
+{
+	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
+
+	ProcessConfigFile(PGC_SIGHUP);
+
+	if (strcmp(pre_standby_slot_names, standby_slot_names) != 0)
+	{
+		list_free(*standby_slots);
+		*standby_slots = GetStandbySlotList(true);
+	}
+
+	pfree(pre_standby_slot_names);
+}
+
+/*
+ * Filter the standby slots based on the specified log sequence number
+ * (wait_for_lsn).
+ *
+ * This function updates the passed standby_slots list, removing any slots that
+ * have already caught up to or surpassed the given wait_for_lsn. Additionally,
+ * it removes slots that have been invalidated, dropped, or converted to
+ * logical slots.
+ */
+static void
+WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
+{
+	ListCell   *lc;
+	List	   *standby_slots_cpy = *standby_slots;
+
+	foreach(lc, standby_slots_cpy)
+	{
+		char	   *name = lfirst(lc);
+		XLogRecPtr	restart_lsn = InvalidXLogRecPtr;
+		bool		invalidated = false;
+		char	   *warningfmt = NULL;
+		ReplicationSlot *slot;
+
+		slot = SearchNamedReplicationSlot(name, true);
+
+		if (slot && SlotIsPhysical(slot))
+		{
+			SpinLockAcquire(&slot->mutex);
+			restart_lsn = slot->data.restart_lsn;
+			invalidated = slot->data.invalidated != RS_INVAL_NONE;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Continue if the current slot hasn't caught up. */
+		if (!invalidated && !XLogRecPtrIsInvalid(restart_lsn) &&
+			restart_lsn < wait_for_lsn)
+		{
+			/* Log warning if no active_pid for this physical slot */
+			if (slot->active_pid == 0)
+				ereport(WARNING,
+						errmsg("replication slot \"%s\" specified in parameter \"%s\" does not have active_pid",
+							   name, "standby_slot_names"),
+						errdetail("Logical replication is waiting on the "
+								  "standby associated with \"%s\"", name),
+						errhint("Consider starting standby associated with "
+								"\"%s\" or amend standby_slot_names", name));
+
+			continue;
+		}
+		else if (!slot)
+		{
+			/*
+			 * It may happen that the slot specified in standby_slot_names GUC
+			 * value is dropped, so let's skip over it.
+			 */
+			warningfmt = _("replication slot \"%s\" specified in parameter \"%s\" does not exist, ignoring");
+		}
+		else if (SlotIsLogical(slot))
+		{
+			/*
+			 * If a logical slot name is provided in standby_slot_names, issue
+			 * a WARNING and skip it. Although logical slots are disallowed in
+			 * the GUC check_hook(validate_standby_slots), it is still
+			 * possible for a user to drop an existing physical slot and
+			 * recreate a logical slot with the same name. Since it is
+			 * harmless, a WARNING should be enough, no need to error-out.
+			 */
+			warningfmt = _("cannot have logical replication slot \"%s\" in parameter \"%s\", ignoring");
+		}
+		else if (XLogRecPtrIsInvalid(restart_lsn) || invalidated)
+		{
+			/*
+			 * Specified physical slot may have been invalidated, so no point
+			 * in waiting for it.
+			 */
+			warningfmt = _("physical slot \"%s\" specified in parameter \"%s\" has been invalidated, ignoring");
+		}
+		else
+		{
+			Assert(restart_lsn >= wait_for_lsn);
+		}
+
+		/*
+		 * Reaching here indicates that either the slot has passed the
+		 * wait_for_lsn or there is an issue with the slot that requires a
+		 * warning to be reported.
+		 */
+		if (warningfmt)
+			ereport(WARNING, errmsg(warningfmt, name, "standby_slot_names"));
+
+		standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
+	}
+
+	*standby_slots = standby_slots_cpy;
+}
+
+/*
+ * Wait for physical standby to confirm receiving the given lsn.
+ *
+ * Used by logical decoding SQL functions that acquired slot with failover
+ * enabled. It waits for physical standbys corresponding to the physical slots
+ * specified in the standby_slot_names GUC.
+ */
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+	List	   *standby_slots;
+
+	Assert(!am_walsender);
+
+	if (!MyReplicationSlot->data.failover)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+
+	for (;;)
+	{
+		long		sleeptime = -1;
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (ConfigReloadPending)
+		{
+			ConfigReloadPending = false;
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
+		}
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
+
+		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, sleeptime,
+									WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+	}
+
+	ConditionVariableCancelSleep();
+	list_free(standby_slots);
+}
+
 /*
  * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
  *
- * Returns end LSN of flushed WAL.  Normally this will be >= loc, but
- * if we detect a shutdown request (either from postmaster or client)
- * we will return early, so caller must always check.
+ * If the walsender holds a logical slot that has enabled failover, we also
+ * wait for all the specified streaming replication standby servers to
+ * confirm receipt of WAL up to RecentFlushPtr.
+ *
+ * Returns end LSN of flushed WAL.  Normally this will be >= loc, but if we
+ * detect a shutdown request (either from postmaster or client) we will return
+ * early, so caller must always check.
  */
 static XLogRecPtr
 WalSndWaitForWal(XLogRecPtr loc)
 {
 	int			wakeEvents;
+	bool		wait_for_standby = false;
+	uint32		wait_event;
+	List	   *standby_slots = NIL;
 	static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
 
+	if (MyReplicationSlot->data.failover)
+		standby_slots = GetStandbySlotList(true);
+
 	/*
-	 * Fast path to avoid acquiring the spinlock in case we already know we
-	 * have enough WAL available. This is particularly interesting if we're
-	 * far behind.
+	 * Check if all the standby servers have confirmed receipt of WAL up to
+	 * RecentFlushPtr even when we already know we have enough WAL available.
+	 *
+	 * Note that we cannot directly return without checking the status of
+	 * standby servers because the standby_slot_names may have changed, which
+	 * means there could be new standby slots in the list that have not yet
+	 * caught up to the RecentFlushPtr.
 	 */
-	if (RecentFlushPtr != InvalidXLogRecPtr &&
-		loc <= RecentFlushPtr)
-		return RecentFlushPtr;
+	if (!XLogRecPtrIsInvalid(RecentFlushPtr) && loc <= RecentFlushPtr)
+	{
+		WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
+		/*
+		 * Fast path to avoid acquiring the spinlock in case we already know
+		 * we have enough WAL available and all the standby servers have
+		 * confirmed receipt of WAL up to RecentFlushPtr. This is particularly
+		 * interesting if we're far behind.
+		 */
+		if (standby_slots == NIL)
+			return RecentFlushPtr;
+	}
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
@@ -1568,7 +1835,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (ConfigReloadPending)
 		{
 			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			WalSndRereadConfigAndReInitSlotList(&standby_slots);
 			SyncRepInitConfig();
 		}
 
@@ -1583,8 +1850,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (got_STOPPING)
 			XLogBackgroundFlush();
 
+		/*
+		 * Update the standby slots that have not yet caught up to the flushed
+		 * position. It is good to wait up to RecentFlushPtr and then let it
+		 * send the changes to logical subscribers one by one which are
+		 * already covered in RecentFlushPtr without needing to wait on every
+		 * change for standby confirmation.
+		 */
+		if (wait_for_standby)
+			WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
+
 		/* Update our idea of the currently flushed position. */
-		if (!RecoveryInProgress())
+		else if (!RecoveryInProgress())
 			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
@@ -1612,9 +1889,18 @@ WalSndWaitForWal(XLogRecPtr loc)
 			!waiting_for_ping_response)
 			WalSndKeepalive(false, InvalidXLogRecPtr);
 
-		/* check whether we're done */
-		if (loc <= RecentFlushPtr)
+		if (loc > RecentFlushPtr)
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL;
+		else if (standby_slots)
+		{
+			wait_event = WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION;
+			wait_for_standby = true;
+		}
+		else
+		{
+			/* already caught up and doesn't need to wait for standby_slots */
 			break;
+		}
 
 		/* Waiting for new WAL. Since we need to wait, we're now caught up. */
 		WalSndCaughtUp = true;
@@ -1654,9 +1940,11 @@ WalSndWaitForWal(XLogRecPtr loc)
 		if (pq_is_send_pending())
 			wakeEvents |= WL_SOCKET_WRITEABLE;
 
-		WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_WAIT_FOR_WAL);
+		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
+	list_free(standby_slots);
+
 	/* reactivate latch so WalSndLoop knows to continue */
 	SetLatch(MyLatch);
 	return RecentFlushPtr;
@@ -1819,6 +2107,13 @@ exec_replication_command(const char *cmd_string)
 			EndReplicationCommand(cmdtag);
 			break;
 
+		case T_AlterReplicationSlotCmd:
+			cmdtag = "ALTER_REPLICATION_SLOT";
+			set_ps_display(cmdtag);
+			AlterReplicationSlot((AlterReplicationSlotCmd *) cmd_node);
+			EndReplicationCommand(cmdtag);
+			break;
+
 		case T_StartReplicationCmd:
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
@@ -2049,6 +2344,7 @@ PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
 	{
 		ReplicationSlotMarkDirty();
 		ReplicationSlotsComputeRequiredLSN();
+		PhysicalWakeupLogicalWalSnd();
 	}
 
 	/*
@@ -3311,6 +3607,8 @@ WalSndShmemInit(void)
 
 		ConditionVariableInit(&WalSndCtl->wal_flush_cv);
 		ConditionVariableInit(&WalSndCtl->wal_replay_cv);
+
+		ConditionVariableInit(&WalSndCtl->wal_confirm_rcv_cv);
 	}
 }
 
@@ -3380,8 +3678,14 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 *
 	 * And, we use separate shared memory CVs for physical and logical
 	 * walsenders for selective wake ups, see WalSndWakeup() for more details.
+	 *
+	 * When the wait event is WAIT_FOR_STANDBY_CONFIRMATION, wait on another
+	 * CV that is woken up by physical walsenders when the walreceiver has
+	 * confirmed the receipt of LSN.
 	 */
-	if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
+	if (wait_event == WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION)
+		ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
+	else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv);
 	else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL)
 		ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index d7995931bd..ede94a1ede 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -76,6 +76,7 @@ LIBPQWALRECEIVER_CONNECT	"Waiting in WAL receiver to establish connection to rem
 LIBPQWALRECEIVER_RECEIVE	"Waiting in WAL receiver to receive data from remote server."
 SSL_OPEN_SERVER	"Waiting for SSL while attempting connection."
 WAL_SENDER_WAIT_FOR_WAL	"Waiting for WAL to be flushed in WAL sender process."
+WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION	"Waiting for the WAL to be received by physical standby in WAL sender process."
 WAL_SENDER_WRITE_DATA	"Waiting for any activity when processing replies from WAL receiver in WAL sender process."
 
 
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 6474e35ec0..4b776266a4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4572,6 +4572,20 @@ struct config_string ConfigureNamesString[] =
 		check_debug_io_direct, assign_debug_io_direct, NULL
 	},
 
+	{
+		{"standby_slot_names", PGC_SIGHUP, REPLICATION_PRIMARY,
+			gettext_noop("Lists streaming replication standby server slot "
+						 "names that logical WAL sender processes will wait for."),
+			gettext_noop("Decoded changes are sent out to plugins by logical "
+						 "WAL sender processes only after specified "
+						 "replication slots confirm receiving WAL."),
+			GUC_LIST_INPUT | GUC_LIST_QUOTE
+		},
+		&standby_slot_names,
+		"",
+		check_standby_slot_names, assign_standby_slot_names, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfe..5d940b72cd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -329,6 +329,8 @@
 				# method to choose sync standbys, number of sync standbys,
 				# and comma-separated list of application_name
 				# from standby(s); '*' = all
+#standby_slot_names = '' # streaming replication standby server slot names that
+				# logical walsender processes will wait for
 
 # - Standby Servers -
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8c0b5486b9..373c2514df 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4618,6 +4618,7 @@ getSubscriptions(Archive *fout)
 	int			i_subsynccommit;
 	int			i_subpublications;
 	int			i_suborigin;
+	int			i_subfailoverstate;
 	int			i,
 				ntups;
 
@@ -4673,14 +4674,22 @@ getSubscriptions(Archive *fout)
 		appendPQExpBufferStr(query,
 							 " s.subpasswordrequired,\n"
 							 " s.subrunasowner,\n"
-							 " s.suborigin\n");
+							 " s.suborigin,\n");
 	else
 		appendPQExpBuffer(query,
 						  " 't' AS subpasswordrequired,\n"
 						  " 't' AS subrunasowner,\n"
-						  " '%s' AS suborigin\n",
+						  " '%s' AS suborigin,\n",
 						  LOGICALREP_ORIGIN_ANY);
 
+	if (fout->remoteVersion >= 170000)
+		appendPQExpBufferStr(query,
+							 " s.subfailoverstate\n");
+	else
+		appendPQExpBuffer(query,
+						  " '%c' AS subfailoverstate\n",
+						  LOGICALREP_FAILOVER_STATE_DISABLED);
+
 	appendPQExpBufferStr(query,
 						 "FROM pg_subscription s\n"
 						 "WHERE s.subdbid = (SELECT oid FROM pg_database\n"
@@ -4709,6 +4718,7 @@ getSubscriptions(Archive *fout)
 	i_subsynccommit = PQfnumber(res, "subsynccommit");
 	i_subpublications = PQfnumber(res, "subpublications");
 	i_suborigin = PQfnumber(res, "suborigin");
+	i_subfailoverstate = PQfnumber(res, "subfailoverstate");
 
 	subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo));
 
@@ -4746,6 +4756,8 @@ getSubscriptions(Archive *fout)
 		subinfo[i].subpublications =
 			pg_strdup(PQgetvalue(res, i, i_subpublications));
 		subinfo[i].suborigin = pg_strdup(PQgetvalue(res, i, i_suborigin));
+		subinfo[i].subfailoverstate =
+			pg_strdup(PQgetvalue(res, i, i_subfailoverstate));
 
 		/* Decide whether we want to dump it */
 		selectDumpableObject(&(subinfo[i].dobj), fout);
@@ -4771,6 +4783,7 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	int			npubnames = 0;
 	int			i;
 	char		two_phase_disabled[] = {LOGICALREP_TWOPHASE_STATE_DISABLED, '\0'};
+	char		failover_disabled[] = {LOGICALREP_FAILOVER_STATE_DISABLED, '\0'};
 
 	/* Do nothing in data-only dump */
 	if (dopt->dataOnly)
@@ -4818,6 +4831,9 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
 	if (strcmp(subinfo->subtwophasestate, two_phase_disabled) != 0)
 		appendPQExpBufferStr(query, ", two_phase = on");
 
+	if (strcmp(subinfo->subfailoverstate, failover_disabled) != 0)
+		appendPQExpBufferStr(query, ", failover = true");
+
 	if (strcmp(subinfo->subdisableonerr, "t") == 0)
 		appendPQExpBufferStr(query, ", disable_on_error = true");
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2fe3cbed9a..f62a4dfd4b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -671,6 +671,7 @@ typedef struct _SubscriptionInfo
 	char	   *subsynccommit;
 	char	   *subpublications;
 	char	   *suborigin;
+	char	   *subfailoverstate;
 } SubscriptionInfo;
 
 /*
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 4878aa22bf..e16286f18c 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -661,7 +661,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 	 * started and stopped several times causing any temporary slots to be
 	 * removed.
 	 */
-	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+	res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
 							"%s as caught_up, conflicting as invalid "
 							"FROM pg_catalog.pg_replication_slots "
 							"WHERE slot_type = 'logical' AND "
@@ -679,6 +679,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		int			i_slotname;
 		int			i_plugin;
 		int			i_twophase;
+		int			i_failover;
 		int			i_caught_up;
 		int			i_invalid;
 
@@ -687,6 +688,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 		i_slotname = PQfnumber(res, "slot_name");
 		i_plugin = PQfnumber(res, "plugin");
 		i_twophase = PQfnumber(res, "two_phase");
+		i_failover = PQfnumber(res, "failover");
 		i_caught_up = PQfnumber(res, "caught_up");
 		i_invalid = PQfnumber(res, "invalid");
 
@@ -697,6 +699,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
 			curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
 			curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
 			curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+			curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
 			curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
 			curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
 		}
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 3960af4036..09f7437716 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
 			appendStringLiteralConn(query, slot_info->slotname, conn);
 			appendPQExpBuffer(query, ", ");
 			appendStringLiteralConn(query, slot_info->plugin, conn);
-			appendPQExpBuffer(query, ", false, %s);",
-							  slot_info->two_phase ? "true" : "false");
+
+			appendPQExpBuffer(query, ", false, %s, %s);",
+							  slot_info->two_phase ? "true" : "false",
+							  slot_info->failover ? "true" : "false");
 
 			PQclear(executeQueryOrDie(conn, "%s", query->data));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index a710f325de..2d8bcb26f9 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -160,6 +160,8 @@ typedef struct
 	bool		two_phase;		/* can the slot decode 2PC? */
 	bool		caught_up;		/* has the slot caught up to latest changes? */
 	bool		invalid;		/* if true, the slot is unusable */
+	bool		failover;		/* is the slot designated to be synced to the
+								 * physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
diff --git a/src/bin/pg_upgrade/t/003_logical_slots.pl b/src/bin/pg_upgrade/t/003_logical_slots.pl
index 020e7aa1cc..cb3a2b3aed 100644
--- a/src/bin/pg_upgrade/t/003_logical_slots.pl
+++ b/src/bin/pg_upgrade/t/003_logical_slots.pl
@@ -159,7 +159,7 @@ $sub->start;
 $sub->safe_psql(
 	'postgres', qq[
 	CREATE TABLE tbl (a int);
-	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true')
+	CREATE SUBSCRIPTION regress_sub CONNECTION '$old_connstr' PUBLICATION regress_pub WITH (two_phase = 'true', failover = 'true')
 ]);
 $sub->wait_for_subscription_sync($oldpub, 'regress_sub');
 
@@ -179,8 +179,8 @@ command_ok([@pg_upgrade_cmd], 'run of pg_upgrade of old cluster');
 # Check that the slot 'regress_sub' has migrated to the new cluster
 $newpub->start;
 my $result = $newpub->safe_psql('postgres',
-	"SELECT slot_name, two_phase FROM pg_replication_slots");
-is($result, qq(regress_sub|t), 'check the slot exists on new cluster');
+	"SELECT slot_name, two_phase, failover FROM pg_replication_slots");
+is($result, qq(regress_sub|t|t), 'check the slot exists on new cluster');
 
 # Update the connection
 my $new_connstr = $newpub->connstr . ' dbname=postgres';
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5077e7b358..36795b1085 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6563,7 +6563,8 @@ describeSubscriptions(const char *pattern, bool verbose)
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
 	static const bool translate_columns[] = {false, false, false, false,
-	false, false, false, false, false, false, false, false, false, false};
+		false, false, false, false, false, false, false, false, false, false,
+	false};
 
 	if (pset.sversion < 100000)
 	{
@@ -6627,6 +6628,11 @@ describeSubscriptions(const char *pattern, bool verbose)
 							  gettext_noop("Password required"),
 							  gettext_noop("Run as owner?"));
 
+		if (pset.sversion >= 170000)
+			appendPQExpBuffer(&buf,
+							  ", subfailoverstate AS \"%s\"\n",
+							  gettext_noop("Failover"));
+
 		appendPQExpBuffer(&buf,
 						  ",  subsynccommit AS \"%s\"\n"
 						  ",  subconninfo AS \"%s\"\n",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 049801186c..905964a2e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3327,7 +3327,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
 	else if (HeadMatches("CREATE", "SUBSCRIPTION") && TailMatches("WITH", "("))
 		COMPLETE_WITH("binary", "connect", "copy_data", "create_slot",
-					  "disable_on_error", "enabled", "origin",
+					  "disable_on_error", "enabled", "failover", "origin",
 					  "password_required", "run_as_owner", "slot_name",
 					  "streaming", "synchronous_commit", "two_phase");
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 77e8b13764..bbc03bb76b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11100,17 +11100,17 @@
   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,bool}',
-  proargmodes => '{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,conflicting}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool}',
+  proargmodes => '{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,conflicting,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index e0b91eacd2..3190a3889b 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -31,6 +31,14 @@
 #define LOGICALREP_TWOPHASE_STATE_PENDING 'p'
 #define LOGICALREP_TWOPHASE_STATE_ENABLED 'e'
 
+/*
+ * failover tri-state values. See comments atop worker.c to know more about
+ * these states.
+ */
+#define LOGICALREP_FAILOVER_STATE_DISABLED 'd'
+#define LOGICALREP_FAILOVER_STATE_PENDING 'p'
+#define LOGICALREP_FAILOVER_STATE_ENABLED 'e'
+
 /*
  * The subscription will request the publisher to only send changes that do not
  * have any origin.
@@ -93,6 +101,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
 	bool		subrunasowner;	/* True if replication should execute as the
 								 * subscription owner */
 
+	char		subfailoverstate;	/* Failover state */
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 	/* Connection string to the publisher */
 	text		subconninfo BKI_FORCE_NOT_NULL;
@@ -145,6 +155,7 @@ typedef struct Subscription
 	List	   *publications;	/* List of publication names to subscribe to */
 	char	   *origin;			/* Only publish data originating from the
 								 * specified origin */
+	char		failoverstate;	/* Allow slot to be synchronized for failover */
 } Subscription;
 
 /* Disallow streaming in-progress transactions. */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index 5142a08729..bef8a7162e 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -72,6 +72,18 @@ typedef struct DropReplicationSlotCmd
 } DropReplicationSlotCmd;
 
 
+/* ----------------------
+ *		ALTER_REPLICATION_SLOT command
+ * ----------------------
+ */
+typedef struct AlterReplicationSlotCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} AlterReplicationSlotCmd;
+
+
 /* ----------------------
  *		START_REPLICATION command
  * ----------------------
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index d3535eed58..5ddad69348 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
 	/* plugin name */
 	NameData	plugin;
+
+	/*
+	 * Is this a failover slot (sync candidate for physical standbys)? Only
+	 * relevant for logical slots on the primary server.
+	 */
+	bool		failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -210,6 +216,7 @@ extern PGDLLIMPORT ReplicationSlot *MyReplicationSlot;
 
 /* GUCs */
 extern PGDLLIMPORT int max_replication_slots;
+extern PGDLLIMPORT char *standby_slot_names;
 
 /* shmem initialization functions */
 extern Size ReplicationSlotsShmemSize(void);
@@ -218,9 +225,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase);
+								  bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
 extern void ReplicationSlotRelease(void);
@@ -253,4 +261,6 @@ extern void CheckPointReplicationSlots(bool is_shutdown);
 extern void CheckSlotRequirements(void);
 extern void CheckSlotPermissions(void);
 
+extern List *GetStandbySlotList(bool copy);
+
 #endif							/* SLOT_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 949e874f21..f1135762fb 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -355,9 +355,20 @@ typedef char *(*walrcv_create_slot_fn) (WalReceiverConn *conn,
 										const char *slotname,
 										bool temporary,
 										bool two_phase,
+										bool failover,
 										CRSSnapshotAction snapshot_action,
 										XLogRecPtr *lsn);
 
+/*
+ * walrcv_alter_slot_fn
+ *
+ * Change the definition of a replication slot. Currently, it only supports
+ * changing the failover property of the slot.
+ */
+typedef void (*walrcv_alter_slot_fn) (WalReceiverConn *conn,
+									  const char *slotname,
+									  bool failover);
+
 /*
  * walrcv_get_backend_pid_fn
  *
@@ -399,6 +410,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_receive_fn walrcv_receive;
 	walrcv_send_fn walrcv_send;
 	walrcv_create_slot_fn walrcv_create_slot;
+	walrcv_alter_slot_fn walrcv_alter_slot;
 	walrcv_get_backend_pid_fn walrcv_get_backend_pid;
 	walrcv_exec_fn walrcv_exec;
 	walrcv_disconnect_fn walrcv_disconnect;
@@ -428,8 +440,10 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_receive(conn, buffer, wait_fd)
 #define walrcv_send(conn, buffer, nbytes) \
 	WalReceiverFunctions->walrcv_send(conn, buffer, nbytes)
-#define walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn) \
-	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, snapshot_action, lsn)
+#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn) \
+	WalReceiverFunctions->walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
+#define walrcv_alter_slot(conn, slotname, failover) \
+	WalReceiverFunctions->walrcv_alter_slot(conn, slotname, failover)
 #define walrcv_get_backend_pid(conn) \
 	WalReceiverFunctions->walrcv_get_backend_pid(conn)
 #define walrcv_exec(conn, exec, nRetTypes, retTypes) \
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 60313980a9..ecd212c7b6 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -12,6 +12,8 @@
 #ifndef _WALSENDER_H
 #define _WALSENDER_H
 
+#include "access/xlogdefs.h"
+
 /*
  * What to do with a snapshot in create replication slot command.
  */
@@ -45,6 +47,8 @@ extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
+extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
 
 /*
  * Remember that we want to wakeup walsenders later
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 13fd5877a6..48c6a7a146 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -113,6 +113,13 @@ typedef struct
 	ConditionVariable wal_flush_cv;
 	ConditionVariable wal_replay_cv;
 
+	/*
+	 * Used by physical walsenders holding slots specified in
+	 * standby_slot_names to wake up logical walsenders holding
+	 * failover-enabled slots when a walreceiver confirms the receipt of LSN.
+	 */
+	ConditionVariable wal_confirm_rcv_cv;
+
 	WalSnd		walsnds[FLEXIBLE_ARRAY_MEMBER];
 } WalSndCtlData;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index db73408937..84bb79ac0f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -256,7 +256,8 @@ extern void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid,
 											   char *originname, Size szoriginname);
 
 extern bool AllTablesyncsReady(void);
-extern void UpdateTwoPhaseState(Oid suboid, char new_state);
+extern void EnableTwoPhaseFailoverTriState(Oid suboid, bool enable_twophase,
+										   bool enable_failover);
 
 extern void process_syncing_tables(XLogRecPtr current_lsn);
 extern void invalidate_syncing_table_states(Datum arg, int cacheid,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 3d74483f44..2f3028cc07 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -162,5 +162,8 @@ extern bool check_wal_consistency_checking(char **newval, void **extra,
 extern void assign_wal_consistency_checking(const char *newval, void *extra);
 extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
 extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern bool check_standby_slot_names(char **newval, void **extra,
+									 GucSource source);
+extern void assign_standby_slot_names(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9d8039684a..3be3ee52fc 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -45,6 +45,7 @@ tests += {
       't/037_invalid_database.pl',
       't/038_save_logical_slots_shutdown.pl',
       't/039_end_of_wal.pl',
+      't/050_verify_slot_order.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recovery/t/006_logical_decoding.pl
index 5025d65b1b..a3c3ee3a14 100644
--- a/src/test/recovery/t/006_logical_decoding.pl
+++ b/src/test/recovery/t/006_logical_decoding.pl
@@ -172,9 +172,10 @@ is($node_primary->slot('otherdb_slot')->{'slot_name'},
 	undef, 'logical slot was actually dropped with DB');
 
 # Test logical slot advancing and its durability.
+# Pass failover=true (last-arg), it should not have any impact on advancing.
 my $logical_slot = 'logical_slot';
 $node_primary->safe_psql('postgres',
-	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false);"
+	"SELECT pg_create_logical_replication_slot('$logical_slot', 'test_decoding', false, false, true);"
 );
 $node_primary->psql(
 	'postgres', "
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
new file mode 100644
index 0000000000..09b5d006a5
--- /dev/null
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -0,0 +1,298 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+##################################################
+# Test primary disallowing specified logical replication slots getting ahead of
+# specified physical replication slots. It uses the following set up:
+#
+#				| ----> standby1 (primary_slot_name = sb1_slot)
+#				| ----> standby2 (primary_slot_name = sb2_slot)
+# primary -----	|
+#				| ----> subscriber1 (failover = true)
+#				| ----> subscriber2 (failover = false)
+#
+# standby_slot_names = 'sb1_slot'
+#
+# Set up is configured in such a way that the logical slot of subscriber1 is
+# enabled failover, thus it will wait for the physical slot of
+# standby1(sb1_slot) to catch up before sending decoded changes to subscriber1.
+##################################################
+
+# Create primary
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+
+# Configure primary to disallow any logical slots that enabled failover from
+# getting ahead of specified physical replication slot (sb1_slot).
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb1_slot'
+));
+$primary->start;
+
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+$primary->psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb2_slot');});
+
+$primary->safe_psql('postgres', "CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Create a standby
+my $standby1 = PostgreSQL::Test::Cluster->new('standby1');
+$standby1->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby1->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb1_slot'
+));
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+
+# Create another standby
+my $standby2 = PostgreSQL::Test::Cluster->new('standby2');
+$standby2->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$standby2->append_conf(
+	'postgresql.conf', qq(
+primary_slot_name = 'sb2_slot'
+));
+$standby2->start;
+$primary->wait_for_replay_catchup($standby2);
+
+# Create publication on primary
+my $publisher = $primary;
+$publisher->safe_psql('postgres',
+	"CREATE PUBLICATION regress_mypub FOR TABLE tab_int;");
+my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
+
+# Create a subscriber node, wait for sync to complete
+my $subscriber1 = PostgreSQL::Test::Cluster->new('subscriber1');
+$subscriber1->init(allows_streaming => 'logical');
+$subscriber1->start;
+$subscriber1->safe_psql('postgres',
+	"CREATE TABLE tab_int (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true
+$subscriber1->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub1 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub1_slot, failover = true);"
+);
+$subscriber1->wait_for_subscription_sync;
+
+# Create another subscriber node without enabling failover, wait for sync to
+# complete
+my $subscriber2 = PostgreSQL::Test::Cluster->new('subscriber2');
+$subscriber2->init(allows_streaming => 'logical');
+$subscriber2->start;
+$subscriber2->safe_psql('postgres',
+	"CREATE TABLE tab_int (a int PRIMARY KEY);");
+$subscriber2->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub2 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub2_slot);");
+$subscriber2->wait_for_subscription_sync;
+
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby comes
+# up.
+$standby1->stop;
+
+# Create some data on primary
+my $primary_row_count   = 10;
+my $primary_insert_time = time();
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# Wait for the standby that's up and running gets the data from primary
+$primary->wait_for_replay_catchup($standby2);
+my $result = $standby2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby2 gets data from primary");
+
+# Wait for the subscription that's up and running and is not enabled for failover.
+# It gets the data from primary without waiting for any standbys.
+$publisher->wait_for_catchup('regress_mysub2');
+$result = $subscriber2->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "subscriber2 gets data from primary");
+
+# The subscription that's up and running and is enabled for failover
+# doesn't get the data from primary and keeps waiting for the
+# standby specified in standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary until standby1 acknowledges changes"
+);
+
+# Start the standby specified in standby_slot_names and wait for it to catch
+# up with the primary.
+$standby1->start;
+$primary->wait_for_replay_catchup($standby1);
+$result = $standby1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't', "standby1 gets data from primary");
+
+# Now that the standby specified in standby_slot_names is up and running,
+# primary must send the decoded changes to subscription enabled for failover
+# While the standby was down, this subscriber didn't receive any data from
+# primary i.e. the primary didn't allow it to go ahead of standby.
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 acknowledges changes");
+
+# Stop the standby associated with specified physical replication slot so that
+# the logical replication slot won't receive changes until the standby slot's
+# restart_lsn is advanced or the slots is removed from the standby_slot_names
+# list
+$publisher->safe_psql('postgres', "TRUNCATE tab_int;");
+$publisher->wait_for_catchup('regress_mysub1');
+$standby1->stop;
+
+##################################################
+# Verify that when using pg_logical_slot_get_changes to consume changes from a
+# logical slot with failover enabled, it will also wait for the slots specified
+# in standby_slot_names to catch up.
+##################################################
+
+# Create a logical 'test_decoding' replication slot with failover enabled
+$publisher->safe_psql('postgres',
+	"SELECT pg_create_logical_replication_slot('test_slot', 'test_decoding', false, false, true);"
+);
+
+my $back_q = $primary->background_psql('postgres', on_error_stop => 0);
+my $pid = $back_q->query('SELECT pg_backend_pid()');
+
+# Try and get changes from the logical slot with failover enabled.
+my $offset = -s $primary->logfile;
+$back_q->query_until(qr//,
+	"SELECT pg_logical_slot_get_changes('test_slot', NULL, NULL);\n");
+
+# Wait until the primary server logs a warning indicating that it is waiting
+# for the sb1_slot to catch up.
+$primary->wait_for_log(
+	qr/WARNING: ( [A-Z0-9]+:)? replication slot \"sb1_slot\" specified in parameter \"standby_slot_names\" does not have active_pid/,
+	$offset);
+
+ok($primary->safe_psql('postgres', "SELECT pg_cancel_backend($pid)"),
+	"cancelling pg_logical_slot_get_changes command");
+
+$publisher->safe_psql('postgres',
+	"SELECT pg_drop_replication_slot('test_slot');"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we manually advance this slot's LSN to the
+# latest position.
+##################################################
+
+# Create some data on primary
+$primary_row_count = 10;
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(1, $primary_row_count);");
+
+# The subscription that's up and running and is enabled for failover doesn't
+# get the data from primary and keeps waiting for the standby specified in
+# standby_slot_names.
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 0 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data from primary as long as standby1 restart_lsn has not been updated"
+);
+
+# Advance the lsn of the standby slot manually
+$primary->safe_psql('postgres',
+	"SELECT * FROM pg_replication_slot_advance('sb1_slot', pg_current_wal_lsn());"
+);
+
+# Now that the standby lsn has advanced, primary must send the decoded
+# changes to the subscription
+$publisher->wait_for_catchup('regress_mysub1');
+$result = $subscriber1->safe_psql('postgres',
+	"SELECT count(*) = $primary_row_count FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1's restart_lsn has been updated"
+);
+
+##################################################
+# Test that logical replication will wait for the user-created inactive
+# physical slot to catch up until we remove the slot from standby_slot_names.
+##################################################
+
+# Create some data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(11,20);");
+
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 10 FROM tab_int;");
+is($result, 't',
+	"subscriber1 doesn't get data as the sb1_slot doesn't catch up");
+
+# Remove the standby from the standby_slot_names list and reload the
+# configuration
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', "''");
+$primary->psql('postgres', "SELECT pg_reload_conf()");
+
+# Now that the standby lsn has advanced, primary must send the decoded
+# changes to the subscription.
+$publisher->wait_for_catchup('regress_mysub1');
+$result =
+  $subscriber1->safe_psql('postgres', "SELECT count(*) = 20 FROM tab_int;");
+is($result, 't',
+	"subscriber1 gets data from primary after standby1 is removed from the standby_slot_names list"
+);
+
+# Put the standby back on the primary_slot_name for the rest of the tests
+$primary->adjust_conf('postgresql.conf', 'standby_slot_names', 'sb1_slot');
+$primary->restart();
+
+##################################################
+# Test that when a subscription with failover enabled is created, it will alter
+# the failover property of the corresponding slot on the publisher.
+##################################################
+
+# Create a slot on the publisher with failover disabled
+$primary->safe_psql('postgres',
+	"SELECT 'init' FROM pg_create_logical_replication_slot('lsub3_slot', 'pgoutput', false, false, false);"
+);
+
+# Confirm that the failover flag on the slot is turned off
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"f",
+	'logical slot has failover false on primary');
+
+# Create another subscription enabling failover
+$subscriber1->safe_psql('postgres',
+	    "CREATE SUBSCRIPTION regress_mysub3 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION regress_mypub WITH (slot_name = lsub3_slot, copy_data=false, failover = true, create_slot = false);"
+);
+
+# Confirm that the failover flag on the slot has now been turned on
+is( $primary->safe_psql(
+		'postgres',
+		q{SELECT failover from pg_replication_slots WHERE slot_name = 'lsub3_slot';}
+	),
+	"t",
+	'logical slot has failover true on primary');
+
+$subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
+
+done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b9..cb3b04aa0c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflicting
-   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, conflicting)
+    l.conflicting,
+    l.failover
+   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, conflicting, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index b15eddbff3..96c614332c 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -116,18 +116,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | none   | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
 \dRs+ regress_testsub4
-                                                                                                           List of subscriptions
-       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                 List of subscriptions
+       Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub4 | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub3;
@@ -145,10 +145,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
 ERROR:  invalid connection string syntax: missing "=" after "foobar" in connection info string
 
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -157,10 +157,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
 ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
 ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | f                 | t             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -176,10 +176,10 @@ ERROR:  unrecognized subscription parameter: "create_slot"
 -- ok
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/12345
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/12345
 (1 row)
 
 -- ok - with lsn = NONE
@@ -188,10 +188,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
 ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
 ERROR:  invalid WAL location (LSN): 0/0
 \dRs+
-                                                                                                               List of subscriptions
-      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                     List of subscriptions
+      Name       |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 BEGIN;
@@ -223,10 +223,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar);
 ERROR:  invalid value for parameter "synchronous_commit": "foobar"
 HINT:  Available values: local, remote_write, remote_apply, on, off.
 \dRs+
-                                                                                                                 List of subscriptions
-        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |           Conninfo           | Skip LSN 
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+------------------------------+----------
- regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | local              | dbname=regress_doesnotexist2 | 0/0
+                                                                                                                       List of subscriptions
+        Name         |           Owner           | Enabled |     Publication     | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |           Conninfo           | Skip LSN 
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+------------------------------+----------
+ regress_testsub_foo | regress_subscription_user | f       | {testpub2,testpub3} | f      | off       | d                | f                | any    | t                 | f             | d        | local              | dbname=regress_doesnotexist2 | 0/0
 (1 row)
 
 -- rename back to keep the rest simple
@@ -255,19 +255,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | t      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (binary = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -279,27 +279,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | parallel  | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication already exists
@@ -314,10 +314,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
 ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
 ERROR:  publication "testpub1" is already in subscription "regress_testsub"
 \dRs+
-                                                                                                                   List of subscriptions
-      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                        List of subscriptions
+      Name       |           Owner           | Enabled |         Publication         | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub,testpub1,testpub2} | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 -- fail - publication used more than once
@@ -332,10 +332,10 @@ ERROR:  publication "testpub3" is not in subscription "regress_testsub"
 -- ok - delete publications
 ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 DROP SUBSCRIPTION regress_testsub;
@@ -371,10 +371,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 --fail - alter of two_phase option not supported.
@@ -383,10 +383,10 @@ ERROR:  unrecognized subscription parameter: "two_phase"
 -- but can alter streaming when two_phase enabled
 ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -396,10 +396,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | on        | p                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -412,18 +412,31 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
 WARNING:  subscription was created, but is not connected
 HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 \dRs+
-                                                                                                           List of subscriptions
-      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Synchronous commit |          Conninfo           | Skip LSN 
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+--------------------+-----------------------------+----------
- regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | off                | dbname=regress_doesnotexist | 0/0
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | t                | any    | t                 | f             | d        | off                | dbname=regress_doesnotexist | 0/0
+(1 row)
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+WARNING:  subscription was created, but is not connected
+HINT:  To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
+\dRs+
+                                                                                                                List of subscriptions
+      Name       |           Owner           | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Synchronous commit |          Conninfo           | Skip LSN 
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------------------+-----------------------------+----------
+ regress_testsub | regress_subscription_user | f       | {testpub}   | f      | off       | d                | f                | any    | t                 | f             | p        | off                | dbname=regress_doesnotexist | 0/0
 (1 row)
 
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 444e563ff3..e4601158b3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -290,6 +290,14 @@ ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
 ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
 DROP SUBSCRIPTION regress_testsub;
 
+-- test failover option
+CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, failover = true);
+
+\dRs+
+
+ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
+DROP SUBSCRIPTION regress_testsub;
+
 -- let's do some tests with pg_create_subscription rather than superuser
 SET SESSION AUTHORIZATION regress_subscription_user3;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ba41149b88..199863c6b5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -85,6 +85,7 @@ AlterOwnerStmt
 AlterPolicyStmt
 AlterPublicationAction
 AlterPublicationStmt
+AlterReplicationSlotCmd
 AlterRoleSetStmt
 AlterRoleStmt
 AlterSeqStmt
@@ -3870,6 +3871,7 @@ varattrib_1b_e
 varattrib_4b
 vbits
 verifier_context
+walrcv_alter_slot_fn
 walrcv_check_conninfo_fn
 walrcv_connect_fn
 walrcv_create_slot_fn
-- 
2.34.1



  [application/octet-stream] v45-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch (7.9K, ../../CAJpy0uBEWO_8vxJFss0CaAw28QgQtKHh_bqs9iZS3YoyOe4Wyg@mail.gmail.com/3-v45-0003-Allow-slot-sync-worker-to-wait-for-the-cascading.patch)
  download | inline diff:
From 82c236f4be380161b187b9ce8b6a6713be2b5b55 Mon Sep 17 00:00:00 2001
From: Shveta Malik <[email protected]>
Date: Mon, 11 Dec 2023 12:28:46 +0530
Subject: [PATCH v45 3/3] Allow slot-sync worker to wait for the cascading
 standbys.

The GUC standby_slot_names is needed to be set on first standby
in order to allow it to wait for confirmation for cascading
standbys before updating logical 'synced' slots in slot-sync worker.
The intent is that the logical slots (synced ones) should not go
ahead of cascading standbys.

For the user created slots on first standby, we already have this wait
logic in place in logical walsender and in pg_logical_slot_get_changes_guts(),
but for synced slots (which can not be consumed yet), we need to make
sure that they are not going ahead of cascading standbys and that is
acheived by introducing the wait in slot-sync worker before we actually
update the slots.
---
 src/backend/replication/logical/slotsync.c | 89 ++++++++++++++++++++--
 src/backend/replication/walsender.c        |  4 +-
 src/include/replication/walsender.h        |  5 ++
 3 files changed, 88 insertions(+), 10 deletions(-)

diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 0a8903ca92..48d4379714 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -112,7 +112,9 @@ static TimestampTz last_update_time;
  */
 #define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
 
-static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn);
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn,
+									  List **standby_slots);
+
 
 /*
  * Wait for remote slot to pass locally reserved position.
@@ -171,7 +173,7 @@ wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
 		CHECK_FOR_INTERRUPTS();
 
 		/* Handle any termination request if any */
-		ProcessSlotSyncInterrupts(wrconn);
+		ProcessSlotSyncInterrupts(wrconn, NULL /* standby_slots */ );
 
 		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
 
@@ -455,6 +457,52 @@ drop_obsolete_slots(List *remote_slot_list)
 	}
 }
 
+/*
+ * Wait for cascading physical standbys corresponding to physical slots
+ * specified in standby_slot_names GUC to confirm receiving given lsn.
+ */
+static void
+wait_for_standby_confirmation(XLogRecPtr wait_for_lsn,
+							  WalReceiverConn *wrconn)
+{
+	List	   *standby_slots;
+
+	/* Nothing to be done */
+	if (strcmp(standby_slot_names, "") == 0)
+		return;
+
+	standby_slots = GetStandbySlotList(true);
+
+	for (;;)
+	{
+		int			rc;
+
+		WalSndFilterStandbySlots(wait_for_lsn, &standby_slots);
+
+		/* Exit if done waiting for every slot. */
+		if (standby_slots == NIL)
+			break;
+
+		/*
+		 * This will reload configuration and will refresh the standby_slots
+		 * as well provided standby_slot_names GUC is changed by the user.
+		 */
+		ProcessSlotSyncInterrupts(wrconn, &standby_slots);
+
+		/*
+		 * XXX: Is waiting for 5 second before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   5000L,
+					   WAIT_EVENT_WAL_SENDER_WAIT_FOR_STANDBY_CONFIRMATION);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+}
+
 /*
  * Synchronize single slot to given position.
  *
@@ -714,6 +762,7 @@ synchronize_slots(WalReceiverConn *wrconn)
 	MemoryContext oldctx = CurrentMemoryContext;
 	ListCell   *lc;
 	bool		slot_updated = false;
+	XLogRecPtr	max_confirmed_lsn = 0;
 
 	/*
 	 * The primary_slot_name is not set yet or WALs not received yet.
@@ -820,6 +869,9 @@ synchronize_slots(WalReceiverConn *wrconn)
 		/* Create list of remote slots */
 		remote_slot_list = lappend(remote_slot_list, remote_slot);
 
+		if (remote_slot->confirmed_lsn > max_confirmed_lsn)
+			max_confirmed_lsn = remote_slot->confirmed_lsn;
+
 		ExecClearTuple(tupslot);
 	}
 
@@ -830,6 +882,17 @@ synchronize_slots(WalReceiverConn *wrconn)
 	 */
 	drop_obsolete_slots(remote_slot_list);
 
+	/*
+	 * If there are cascading standbys, wait for their confirmation before we
+	 * update synced logical slots locally.
+	 *
+	 * Instead of waiting on confirmation for lsn of each slot, let us wait
+	 * once for confirmation on max_confirmed_lsn. If that is confirmed by
+	 * each cascading standby, we are good to update all the slots.
+	 */
+	if (remote_slot_list)
+		wait_for_standby_confirmation(max_confirmed_lsn, wrconn);
+
 	/* Now sync the slots locally */
 	foreach(lc, remote_slot_list)
 	{
@@ -1001,7 +1064,7 @@ validate_slotsync_parameters(char **dbname)
  * worker will exit if the check fails.
  */
 static void
-slotsync_reread_config(WalReceiverConn *wrconn)
+slotsync_reread_config(WalReceiverConn *wrconn, List **standby_slots)
 {
 	char	   *old_primary_conninfo = pstrdup(PrimaryConnInfo);
 	char	   *old_primary_slot_name = pstrdup(PrimarySlotName);
@@ -1014,8 +1077,14 @@ slotsync_reread_config(WalReceiverConn *wrconn)
 	old_dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
 	Assert(old_dbname);
 
-	ConfigReloadPending = false;
-	ProcessConfigFile(PGC_SIGHUP);
+	/*
+	 * Reload configs and recreate the standby_slot_names_list if GUC
+	 * standby_slot_names changed.
+	 */
+	if (standby_slots)
+		WalSndRereadConfigAndReInitSlotList(standby_slots);
+	else
+		ProcessConfigFile(PGC_SIGHUP);
 
 	conninfoChanged = strcmp(old_primary_conninfo, PrimaryConnInfo) != 0;
 	slotnameChanged = strcmp(old_primary_slot_name, PrimarySlotName) != 0;
@@ -1051,7 +1120,8 @@ slotsync_reread_config(WalReceiverConn *wrconn)
  * Interrupt handler for main loop of slot sync worker.
  */
 static void
-ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn,
+						  List **standby_slots)
 {
 	CHECK_FOR_INTERRUPTS();
 
@@ -1066,7 +1136,10 @@ ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
 	}
 
 	if (ConfigReloadPending)
-		slotsync_reread_config(wrconn);
+	{
+		ConfigReloadPending = false;
+		slotsync_reread_config(wrconn, standby_slots);
+	}
 }
 
 /*
@@ -1142,7 +1215,7 @@ ReplSlotSyncWorkerMain(Datum main_arg)
 		TimestampTz now;
 		bool		slot_updated;
 
-		ProcessSlotSyncInterrupts(wrconn);
+		ProcessSlotSyncInterrupts(wrconn, NULL /* standby_slots */ );
 
 		slot_updated = synchronize_slots(wrconn);
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 6b65c3c7dc..5ea47430a7 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1609,7 +1609,7 @@ PhysicalWakeupLogicalWalSnd(void)
  * Reload the config file and reinitialize the standby slot list if the GUC
  * standby_slot_names has changed.
  */
-static void
+void
 WalSndRereadConfigAndReInitSlotList(List **standby_slots)
 {
 	char	   *pre_standby_slot_names = pstrdup(standby_slot_names);
@@ -1634,7 +1634,7 @@ WalSndRereadConfigAndReInitSlotList(List **standby_slots)
  * it removes slots that have been invalidated, dropped, or converted to
  * logical slots.
  */
-static void
+void
 WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn, List **standby_slots)
 {
 	ListCell   *lc;
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index ecd212c7b6..07b2c719ae 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -13,6 +13,7 @@
 #define _WALSENDER_H
 
 #include "access/xlogdefs.h"
+#include "nodes/pg_list.h"
 
 /*
  * What to do with a snapshot in create replication slot command.
@@ -48,7 +49,11 @@ extern void WalSndWaitStopping(void);
 extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 extern void PhysicalWakeupLogicalWalSnd(void);
+extern void WalSndFilterStandbySlots(XLogRecPtr wait_for_lsn,
+									 List **standby_slots);
 extern void WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn);
+extern List *WalSndGetStandbySlots(void);
+extern void WalSndRereadConfigAndReInitSlotList(List **standby_slots);
 
 /*
  * Remember that we want to wakeup walsenders later
-- 
2.34.1



  [application/octet-stream] v45-0002-Add-logical-slot-sync-capability-to-the-physical.patch (91.0K, ../../CAJpy0uBEWO_8vxJFss0CaAw28QgQtKHh_bqs9iZS3YoyOe4Wyg@mail.gmail.com/4-v45-0002-Add-logical-slot-sync-capability-to-the-physical.patch)
  download | inline diff:
From 08c0380b6b2988996e52beb2fc5cbe2d5d8d75c4 Mon Sep 17 00:00:00 2001
From: Hou Zhijie <[email protected]>
Date: Mon, 11 Dec 2023 10:01:17 +0800
Subject: [PATCH v45 2/3] Add logical slot sync capability to the physical
 standby

This patch implements synchronization of logical replication slots
from the primary server to the physical standby so that logical
replication can be resumed after failover. All the failover logical
replication slots on the primary (assuming configurations are
appropriate) are automatically created on the physical standbys and
are synced periodically. Slot-sync worker on the standby server
ping the primary server at regular intervals to get the necessary
failover logical slots information and create/update the slots locally.

GUC 'enable_syncslot' enables a physical standby to synchronize failover
logical replication slots from the primary server.

The nap time of the worker is tuned according to the activity on the primary.
The worker starts with nap time of 10ms and if no activity is observed on
the primary for some time, then nap time is increased to 10sec. If
activity is observed again, nap time is reduced back to 10ms.

The logical slots created by slot-sync worker on physical standbys are not
allowed to be dropped or consumed. Any attempt to perform logical decoding on
such slots will result in an error.

If a logical slot is invalidated on the primary, slot on the standby is also
invalidated.

If a logical slot on the primary is valid but is invalidated on the standby,
then that slot is dropped and recreated on the standby in next sync-cycle. It
is okay to recreate such slots as long as these are not consumable on the
standby (which is the case currently). This situation may occur due to the
following reasons:
- The max_slot_wal_keep_size on the standby is insufficient to retain WAL
  records from the restart_lsn of the slot.
- primary_slot_name is temporarily reset to null and the physical slot is
  removed.
- The primary changes wal_level to a level lower than logical.

The slots synchronization status on the standby can be monitored using
'sync_state' column of pg_replication_slots view. The values are:
'n': none for user slots,
'i': sync initiated for the slot but slot is not ready yet for periodic syncs,
'r': ready for periodic syncs.
---
 doc/src/sgml/bgworker.sgml                    |   64 +-
 doc/src/sgml/config.sgml                      |   32 +-
 doc/src/sgml/logicaldecoding.sgml             |   33 +
 doc/src/sgml/system-views.sgml                |   26 +
 src/backend/access/transam/xlogrecovery.c     |   10 +
 src/backend/catalog/system_views.sql          |    3 +-
 src/backend/postmaster/bgworker.c             |    4 +
 src/backend/postmaster/postmaster.c           |   10 +
 .../libpqwalreceiver/libpqwalreceiver.c       |   43 +-
 src/backend/replication/logical/Makefile      |    1 +
 src/backend/replication/logical/logical.c     |   25 +
 src/backend/replication/logical/meson.build   |    1 +
 src/backend/replication/logical/slotsync.c    | 1294 +++++++++++++++++
 src/backend/replication/logical/tablesync.c   |    1 +
 src/backend/replication/logical/worker.c      |   15 +-
 src/backend/replication/slot.c                |   23 +-
 src/backend/replication/slotfuncs.c           |   37 +-
 src/backend/replication/walsender.c           |    6 +-
 src/backend/storage/ipc/ipci.c                |    2 +
 src/backend/tcop/postgres.c                   |   11 +
 .../utils/activity/wait_event_names.txt       |    2 +
 src/backend/utils/misc/guc_tables.c           |   10 +
 src/backend/utils/misc/postgresql.conf.sample |    1 +
 src/include/catalog/pg_proc.dat               |   10 +-
 src/include/commands/subscriptioncmds.h       |    4 +
 src/include/postmaster/bgworker.h             |    1 +
 src/include/replication/logicalworker.h       |    1 +
 src/include/replication/slot.h                |   22 +-
 src/include/replication/walreceiver.h         |   19 +
 src/include/replication/worker_internal.h     |   11 +
 .../t/050_standby_failover_slots_sync.pl      |  209 +++
 src/test/regress/expected/rules.out           |    5 +-
 src/test/regress/expected/sysviews.out        |    3 +-
 src/tools/pgindent/typedefs.list              |    2 +
 34 files changed, 1905 insertions(+), 36 deletions(-)
 create mode 100644 src/backend/replication/logical/slotsync.c

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2c393385a9..ecde4fa61f 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -114,18 +114,58 @@ typedef struct BackgroundWorker
 
   <para>
    <structfield>bgw_start_time</structfield> is the server state during which
-   <command>postgres</command> should start the process; it can be one of
-   <literal>BgWorkerStart_PostmasterStart</literal> (start as soon as
-   <command>postgres</command> itself has finished its own initialization; processes
-   requesting this are not eligible for database connections),
-   <literal>BgWorkerStart_ConsistentState</literal> (start as soon as a consistent state
-   has been reached in a hot standby, allowing processes to connect to
-   databases and run read-only queries), and
-   <literal>BgWorkerStart_RecoveryFinished</literal> (start as soon as the system has
-   entered normal read-write state).  Note the last two values are equivalent
-   in a server that's not a hot standby.  Note that this setting only indicates
-   when the processes are to be started; they do not stop when a different state
-   is reached.
+   <command>postgres</command> should start the process. Note that this setting
+   only indicates when the processes are to be started; they do not stop when
+   a different state is reached. Possible values are:
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>BgWorkerStart_PostmasterStart</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_PostmasterStart</primary></indexterm>
+       Start as soon as postgres itself has finished its own initialization;
+       processes requesting this are not eligible for database connections.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState</primary></indexterm>
+       Start as soon as a consistent state has been reached in a hot-standby,
+       allowing processes to connect to databases and run read-only queries.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_RecoveryFinished</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_RecoveryFinished</primary></indexterm>
+       Start as soon as the system has entered normal read-write state. Note
+       that the <literal>BgWorkerStart_ConsistentState</literal> and
+      <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
+       in a server that's not a hot standby.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState_HotStandby</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState_HotStandby</primary></indexterm>
+       Same meaning as <literal>BgWorkerStart_ConsistentState</literal> but
+       it is more strict in terms of the server i.e. start the worker only
+       if it is hot-standby.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
   </para>
 
   <para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 30d9b53e03..1754bcaa1f 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4373,6 +4373,12 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
         meant to switch to a physical standby after the standby is promoted,
         the physical replication slot for the standby should be listed here.
        </para>
+       <para>
+        The standbys corresponding to the physical replication slots in
+        <varname>standby_slot_names</varname> must configure
+        <varname>enable_syncslot</varname> = true so they can receive
+        failover logical slots changes from the primary.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -4568,8 +4574,12 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
           <varname>primary_conninfo</varname> string, or in a separate
           <filename>~/.pgpass</filename> file on the standby server (use
           <literal>replication</literal> as the database name).
-          Do not specify a database name in the
-          <varname>primary_conninfo</varname> string.
+         </para>
+         <para>
+          If slot synchronization is enabled then it is also necessary to
+          specify <literal>dbname</literal> in the
+          <varname>primary_conninfo</varname> string. This will only be used for
+          slot synchronization. It is ignored for streaming.
          </para>
          <para>
           This parameter can only be set in the <filename>postgresql.conf</filename>
@@ -4894,6 +4904,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-syncslot" xreflabel="enable_syncslot">
+      <term><varname>enable_syncslot</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>enable_syncslot</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers are not blocked
+        after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index cd152d4ced..43537e00b2 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -346,6 +346,39 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <function>pg_log_standby_snapshot</function> function on the primary.
     </para>
 
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and setting
+     <varname>enable_syncslot</varname> on the standby. For the synchronization
+     to work, it is mandatory to have a physical replication slot between the
+     primary and the standby. It's highly recommended that the said physical
+     replication slot is listed in <varname>standby_slot_names</varname> on
+     the primary to prevent the subscriber from consuming changes faster than
+     the hot standby. Additionally, <varname>hot_standby_feedback</varname>
+     must be enabled on the standby for the slots synchronization to work.
+    </para>
+
+    <para>
+     By enabling synchronization of slots, logical replication can be resumed
+     after failover depending upon the
+     <link linkend="view-pg-replication-slots">pg_replication_slots</link>.<structfield>sync_state</structfield>
+     for the synchronized slots on the standby at the time of failover.
+     Only slots that were in ready sync_state ('r') on the standby before
+     failover can be used for logical replication after failover.
+     However, the slots which were in initiated sync_state ('i') and
+     not sync-ready ('r') at the time of failover will be dropped and
+     logical replication for such slots can not be resumed after failover.
+     This applies to the case where a logical subscription is disabled
+     before failover and is enabled after failover. If the synchronized
+     slot due to disabled subscription could not be made sync-ready ('r')
+     on standby, then the subscription can not be resumed after failover
+     even when enabled.
+     If the primary is idle, then the synchronized slots on the standby may
+     take a noticeable time to reach the ready ('r') sync_state. This can
+     be sped up by calling the
+     <function>pg_log_standby_snapshot</function> function on the primary.
+    </para>
+
     <caution>
      <para>
       Replication slots persist across crashes and know nothing about the state
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 1dc695fd3a..c906f2186e 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -2543,6 +2543,32 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        after failover. Always false for physical slots.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sync_state</structfield> <type>char</type>
+      </para>
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has configured <varname>enable_syncslot</varname> = true
+      </para>
+      <para>
+       State code:
+       <literal>n</literal> = none for user created slots,
+       <literal>i</literal> = sync initiated for the slot but slot is not ready
+        yet for periodic syncs,
+       <literal>r</literal> = ready for periodic syncs.
+      </para>
+      <para>
+      The hot standby can have any of these sync_state for the slots but on a
+      hot standby, the slots with state 'r' and 'i' can neither be used for
+      logical decoding nor dropped by the user. The primary server will have
+      sync_state as 'n' for all the slots. But if the standby is promoted to
+      become the new primary server, sync_state can be seen 'r' as well. On
+      this new primary server, slots with sync_state as 'r' and 'n' will
+      behave the same.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4bc4d3e323..ec4aed727b 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -1435,6 +1436,15 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync workers to prevent potential conflicts between
+	 * user processes and slotsync workers after a promotion. Additionally,
+	 * drop any slots that have initiated but not yet completed the sync
+	 * process.
+	 */
+	ShutDownSlotSync();
+	slotsync_drop_initiated_slots();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 63038f87f7..c4b3e8a807 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1024,7 +1024,8 @@ CREATE VIEW pg_replication_slots AS
             L.safe_wal_size,
             L.two_phase,
             L.conflicting,
-            L.failover
+            L.failover,
+            L.sync_state
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 3c99cf6047..7f74f53ad1 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -21,6 +21,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "replication/worker_internal.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -129,6 +130,9 @@ static const struct
 	{
 		"ApplyWorkerMain", ApplyWorkerMain
 	},
+	{
+		"ReplSlotSyncWorkerMain", ReplSlotSyncWorkerMain
+	},
 	{
 		"ParallelApplyWorkerMain", ParallelApplyWorkerMain
 	},
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 651b85ea74..9b74a49663 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -115,6 +115,7 @@
 #include "postmaster/syslogger.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pg_shmem.h"
@@ -1003,6 +1004,12 @@ PostmasterMain(int argc, char *argv[])
 	 */
 	ApplyLauncherRegister();
 
+	/*
+	 * Register the slot sync worker here to kick start slot-sync operation
+	 * sooner on the physical standby.
+	 */
+	SlotSyncWorkerRegister();
+
 	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
@@ -5740,6 +5747,9 @@ bgworker_should_start_now(BgWorkerStartTime start_time)
 		case PM_HOT_STANDBY:
 			if (start_time == BgWorkerStart_ConsistentState)
 				return true;
+			if (start_time == BgWorkerStart_ConsistentState_HotStandby &&
+					 pmState != PM_RUN)
+				return true;
 			/* fall through */
 
 		case PM_RECOVERY:
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index ff65fdb4d9..a3c2290147 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -34,6 +34,7 @@
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/varlena.h"
 
 PG_MODULE_MAGIC;
 
@@ -58,6 +59,7 @@ static void libpqrcv_get_senderinfo(WalReceiverConn *conn,
 									char **sender_host, int *sender_port);
 static char *libpqrcv_identify_system(WalReceiverConn *conn,
 									  TimeLineID *primary_tli);
+static char *libpqrcv_get_dbname_from_conninfo(const char *conninfo);
 static int	libpqrcv_server_version(WalReceiverConn *conn);
 static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 											 TimeLineID tli, char **filename,
@@ -100,6 +102,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = {
 	.walrcv_send = libpqrcv_send,
 	.walrcv_create_slot = libpqrcv_create_slot,
 	.walrcv_alter_slot = libpqrcv_alter_slot,
+	.walrcv_get_dbname_from_conninfo = libpqrcv_get_dbname_from_conninfo,
 	.walrcv_get_backend_pid = libpqrcv_get_backend_pid,
 	.walrcv_exec = libpqrcv_exec,
 	.walrcv_disconnect = libpqrcv_disconnect
@@ -418,6 +421,44 @@ libpqrcv_server_version(WalReceiverConn *conn)
 	return PQserverVersion(conn->streamConn);
 }
 
+/*
+ * Get database name from the primary server's conninfo.
+ *
+ * If dbname is not found in connInfo, return NULL value.
+ */
+static char *
+libpqrcv_get_dbname_from_conninfo(const char *connInfo)
+{
+	PQconninfoOption *opts;
+	char	   *dbname = NULL;
+	char	   *err = NULL;
+
+	opts = PQconninfoParse(connInfo, &err);
+	if (opts == NULL)
+	{
+		/* The error string is malloc'd, so we must free it explicitly */
+		char	   *errcopy = err ? pstrdup(err) : "out of memory";
+
+		PQfreemem(err);
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("invalid connection string syntax: %s", errcopy)));
+	}
+
+	for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
+	{
+		/*
+		 * If multiple dbnames are specified, then the last one will be
+		 * returned
+		 */
+		if (strcmp(opt->keyword, "dbname") == 0 && opt->val &&
+			opt->val[0] != '\0')
+			dbname = pstrdup(opt->val);
+	}
+
+	return dbname;
+}
+
 /*
  * Start streaming WAL data from given streaming options.
  *
@@ -997,7 +1038,7 @@ libpqrcv_create_slot(WalReceiverConn *conn, const char *slotname,
  */
 static void
 libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
-					bool failover)
+					 bool failover)
 {
 	StringInfoData cmd;
 	PGresult   *res;
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 2dc25e37bb..ba03eeff1c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	proto.o \
 	relation.o \
 	reorderbuffer.o \
+	slotsync.o \
 	snapbuild.o \
 	tablesync.o \
 	worker.o
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8288da5277..c21d081346 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -524,6 +524,31 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 				 errmsg("replication slot \"%s\" was not created in this database",
 						NameStr(slot->data.name))));
 
+	if (RecoveryInProgress())
+	{
+		/*
+		 * Do not allow consumption of a "synchronized" slot until the standby
+		 * gets promoted.
+		 */
+		if (slot->data.sync_state != SYNCSLOT_STATE_NONE)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot use replication slot \"%s\" for logical "
+						"decoding", NameStr(slot->data.name)),
+					 errdetail("This slot is being synced from the primary server."),
+					 errhint("Specify another replication slot.")));
+	}
+	else
+	{
+		/*
+		 * Slots in state SYNCSLOT_STATE_INITIATED should have been dropped on
+		 * promotion.
+		 */
+		if (slot->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			elog(ERROR, "replication slot \"%s\" was not synced completely "
+				"from the primary server", NameStr(slot->data.name));
+	}
+
 	/*
 	 * Check if slot has been invalidated due to max_slot_wal_keep_size. Avoid
 	 * "cannot get changes" wording in this errmsg because that'd be
diff --git a/src/backend/replication/logical/meson.build b/src/backend/replication/logical/meson.build
index d48cd4c590..9e52ec421f 100644
--- a/src/backend/replication/logical/meson.build
+++ b/src/backend/replication/logical/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'proto.c',
   'relation.c',
   'reorderbuffer.c',
+  'slotsync.c',
   'snapbuild.c',
   'tablesync.c',
   'worker.c',
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
new file mode 100644
index 0000000000..0a8903ca92
--- /dev/null
+++ b/src/backend/replication/logical/slotsync.c
@@ -0,0 +1,1294 @@
+/*-------------------------------------------------------------------------
+ * slotsync.c
+ *	   PostgreSQL worker for synchronizing slots to a standby server from the
+ *         primary server.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/slotsync.c
+ *
+ * This file contains the code for slot sync worker on a physical standby
+ * to fetch logical failover slots information from the primary server,
+ * create the slots on the standby and synchronize them periodically.
+ *
+ * While creating the slot on physical standby, if the local restart_lsn and/or
+ * local catalog_xmin is ahead of those on the remote then the worker cannot
+ * create the local slot in sync with the primary server because that would
+ * mean moving the local slot backwards and the standby might not have WALs
+ * retained for old LSN. In this case, the worker will wait for the primary
+ * server slot's restart_lsn and catalog_xmin to catch up with the local one
+ * before attempting the actual sync. Meanwhile, it will persist the slot with
+ * sync_state as SYNCSLOT_STATE_INITIATED('i'). Once the primary server catches
+ * up, it will move the slot to SYNCSLOT_STATE_READY('r') state and will perform
+ * the sync periodically.
+ *
+ * The worker also takes care of dropping the slots which were created by it
+ * and are currently not needed to be synchronized.
+ *
+ * It takes a nap of WORKER_DEFAULT_NAPTIME_MS before every next
+ * synchronization. If there is no activity observed on the primary server for
+ * some time, the nap time is increased to WORKER_INACTIVITY_NAPTIME_MS, but if
+ * any activity is observed, the nap time reverts to the default value.
+ *---------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/table.h"
+#include "access/xlogrecovery.h"
+#include "catalog/pg_database.h"
+#include "commands/dbcommands.h"
+#include "pgstat.h"
+#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
+#include "replication/logical.h"
+#include "replication/logicallauncher.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+#include "storage/ipc.h"
+#include "storage/procarray.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc_hooks.h"
+#include "utils/pg_lsn.h"
+#include "utils/varlena.h"
+
+/*
+ * Structure to hold information fetched from the primary server about a logical
+ * replication slot.
+ */
+typedef struct RemoteSlot
+{
+	char	   *name;
+	char	   *plugin;
+	char	   *database;
+	bool		two_phase;
+	bool		failover;
+	XLogRecPtr	restart_lsn;
+	XLogRecPtr	confirmed_lsn;
+	TransactionId catalog_xmin;
+
+	/* RS_INVAL_NONE if valid, or the reason of invalidation */
+	ReplicationSlotInvalidationCause invalidated;
+} RemoteSlot;
+
+/*
+ * Struct for sharing information between startup process and slot
+ * sync worker.
+ *
+ * Slot sync worker's pid is needed by startup process in order to
+ * shut it down during promotion.
+ */
+typedef struct SlotSyncWorkerCtx
+{
+	pid_t		pid;
+	slock_t		mutex;
+} SlotSyncWorkerCtx;
+
+SlotSyncWorkerCtx *SlotSyncWorker = NULL;
+
+/* GUC variable */
+bool		enable_syncslot = false;
+
+/* The last sync-cycle time when the worker updated any of the slots. */
+static TimestampTz last_update_time;
+
+/* Worker's nap time in case of regular activity on the primary server */
+#define WORKER_DEFAULT_NAPTIME_MS                   10L /* 10 ms */
+
+/* Worker's nap time in case of no-activity on the primary server */
+#define WORKER_INACTIVITY_NAPTIME_MS                10000L	/* 10 sec */
+
+/*
+ * Inactivity Threshold in ms before increasing nap time of worker.
+ *
+ * If the lsn of slot being monitored did not change for this threshold time,
+ * then increase nap time of current worker from WORKER_DEFAULT_NAPTIME_MS to
+ * WORKER_INACTIVITY_NAPTIME_MS.
+ */
+#define WORKER_INACTIVITY_THRESHOLD_MS 10000L	/* 10 sec */
+
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn);
+
+/*
+ * Wait for remote slot to pass locally reserved position.
+ *
+ * Ping and wait for the primary server for
+ * WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS during a slot creation, if it still
+ * does not catch up, abort the wait. The ones for which wait is aborted will
+ * attempt the wait and sync in the next sync-cycle.
+ *
+ * If passed, *wait_attempts_exceeded will be set to true only if this
+ * function exits after exhausting its wait attempts. It will be false
+ * in all the other cases like failure, remote-slot invalidation, primary
+ * could catch up.
+ */
+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+							  bool *wait_attempts_exceeded)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
+
+	StringInfoData cmd;
+	int			wait_count = 0;
+
+
+	ereport(LOG,
+			errmsg("waiting for remote slot \"%s\" LSN (%X/%X) and catalog xmin"
+				   " (%u) to pass local slot LSN (%X/%X) and catalog xmin (%u)",
+				   remote_slot->name,
+				   LSN_FORMAT_ARGS(remote_slot->restart_lsn),
+				   remote_slot->catalog_xmin,
+				   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+				   MyReplicationSlot->data.catalog_xmin));
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT conflicting, restart_lsn,"
+					 " confirmed_flush_lsn, catalog_xmin"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE slot_name = %s",
+					 quote_literal_cstr(remote_slot->name));
+
+	for (;;)
+	{
+		XLogRecPtr	new_invalidated;
+		XLogRecPtr	new_restart_lsn;
+		XLogRecPtr	new_confirmed_lsn;
+		TransactionId new_catalog_xmin;
+		WalRcvExecResult *res;
+		TupleTableSlot *slot;
+		int			rc;
+		bool		isnull;
+		Oid			slotRow[WAIT_OUTPUT_COLUMN_COUNT] = {BOOLOID, LSNOID, LSNOID,
+		XIDOID};
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Handle any termination request if any */
+		ProcessSlotSyncInterrupts(wrconn);
+
+		res = walrcv_exec(wrconn, cmd.data, WAIT_OUTPUT_COLUMN_COUNT, slotRow);
+
+		if (res->status != WALRCV_OK_TUPLES)
+			ereport(ERROR,
+					(errmsg("could not fetch slot \"%s\" info from the"
+							" primary server: %s",
+							remote_slot->name, res->err)));
+
+		slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+		if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" creation aborted", remote_slot->name),
+					 errdetail("This slot was not found on the primary server")));
+			pfree(cmd.data);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		new_invalidated = DatumGetBool(slot_getattr(slot, 1, &isnull));
+		Assert(!isnull);
+
+		new_restart_lsn = DatumGetLSN(slot_getattr(slot, 2, &isnull));
+		if (new_invalidated || isnull)
+		{
+			ereport(WARNING,
+					(errmsg("slot \"%s\" creation aborted", remote_slot->name),
+					 errdetail("This slot was invalidated on the primary server")));
+			pfree(cmd.data);
+			ExecClearTuple(slot);
+			walrcv_clear_result(res);
+
+			return false;
+		}
+
+		/*
+		 * Having got a valid restart_lsn, the confirmed_lsn and catalog_xmin
+		 * are expected to be valid/non-null.
+		 */
+		new_confirmed_lsn = DatumGetLSN(slot_getattr(slot, 3, &isnull));
+		Assert(!isnull);
+
+		new_catalog_xmin = DatumGetTransactionId(slot_getattr(slot,
+															  4, &isnull));
+		Assert(!isnull);
+
+		ExecClearTuple(slot);
+		walrcv_clear_result(res);
+
+		if (new_restart_lsn >= MyReplicationSlot->data.restart_lsn &&
+			TransactionIdFollowsOrEquals(new_catalog_xmin,
+										 MyReplicationSlot->data.catalog_xmin))
+		{
+			/* Update new values in remote_slot */
+			remote_slot->restart_lsn = new_restart_lsn;
+			remote_slot->confirmed_lsn = new_confirmed_lsn;
+			remote_slot->catalog_xmin = new_catalog_xmin;
+
+			ereport(LOG,
+					errmsg("wait over for remote slot \"%s\" as its LSN (%X/%X)"
+						   " and catalog xmin (%u) has now passed local slot LSN"
+						   " (%X/%X) and catalog xmin (%u)",
+						   remote_slot->name,
+						   LSN_FORMAT_ARGS(new_restart_lsn),
+						   new_catalog_xmin,
+						   LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+						   MyReplicationSlot->data.catalog_xmin));
+			pfree(cmd.data);
+
+			return true;
+		}
+
+		if (++wait_count >= WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS)
+		{
+			ereport(LOG,
+					errmsg("aborting the wait for remote slot \"%s\"",
+						   remote_slot->name));
+			pfree(cmd.data);
+
+			if (wait_attempts_exceeded)
+				*wait_attempts_exceeded = true;
+
+			return false;
+		}
+
+		/*
+		 * XXX: Is waiting for 2 seconds before retrying enough or more or
+		 * less?
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   2000L,
+					   WAIT_EVENT_REPL_SLOTSYNC_PRIMARY_CATCHUP);
+
+		ResetLatch(MyLatch);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+	}
+}
+
+/*
+ * Update local slot metadata as per remote_slot's positions
+ */
+static void
+local_slot_update(RemoteSlot *remote_slot)
+{
+	Assert(MyReplicationSlot->data.invalidated == RS_INVAL_NONE);
+
+	LogicalConfirmReceivedLocation(remote_slot->confirmed_lsn);
+	LogicalIncreaseXminForSlot(remote_slot->confirmed_lsn,
+							   remote_slot->catalog_xmin);
+	LogicalIncreaseRestartDecodingForSlot(remote_slot->confirmed_lsn,
+										  remote_slot->restart_lsn);
+
+	SpinLockAcquire(&MyReplicationSlot->mutex);
+	MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+	SpinLockRelease(&MyReplicationSlot->mutex);
+
+	ReplicationSlotMarkDirty();
+}
+
+/*
+ * Drop the slots for which sync is initiated but not yet completed
+ * i.e. they are still waiting for the primary server to catch up (refer
+ * to the comment atop the file for details on this wait)
+ */
+void
+slotsync_drop_initiated_slots(void)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->in_use && s->data.sync_state == SYNCSLOT_STATE_INITIATED)
+			local_slots = lappend(local_slots, s);
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *s = (ReplicationSlot *) lfirst(lc);
+
+		ReplicationSlotDrop(NameStr(s->data.name), true, false);
+		ereport(LOG,
+				(errmsg("dropped replication slot \"%s\" of dbid %d as it "
+						"was not sync-ready", NameStr(s->data.name),
+						s->data.database)));
+	}
+
+	list_free(local_slots);
+}
+
+/*
+ * Get list of local logical slots which are synchronized from
+ * the primary server.
+ */
+static List *
+get_local_synced_slots(void)
+{
+	List	   *local_slots = NIL;
+
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
+
+	for (int i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		/* Check if it is logical synchronized slot */
+		if (s->in_use && SlotIsLogical(s) &&
+			(s->data.sync_state != SYNCSLOT_STATE_NONE))
+		{
+			local_slots = lappend(local_slots, s);
+		}
+	}
+
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return local_slots;
+}
+
+/*
+ * Helper function to check if local_slot is present in remote_slots list.
+ *
+ * It also checks if logical slot is locally invalidated i.e. invalidated on
+ * the standby but valid on the primary server. If found so, it sets
+ * locally_invalidated to true.
+ */
+static bool
+check_sync_slot_on_remote(ReplicationSlot *local_slot, List *remote_slots,
+						  bool *locally_invalidated)
+{
+	ListCell   *lc;
+
+	foreach(lc, remote_slots)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
+		{
+			/*
+			 * If remote slot is not invalidated but local slot is marked as
+			 * invalidated, then set the bool.
+			 */
+			SpinLockAcquire(&local_slot->mutex);
+			*locally_invalidated =
+				(remote_slot->invalidated == RS_INVAL_NONE) &&
+				(local_slot->data.invalidated != RS_INVAL_NONE);
+			SpinLockRelease(&local_slot->mutex);
+
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Drop obsolete slots
+ *
+ * Drop the slots that no longer need to be synced i.e. these either do not
+ * exist on the primary or are no longer enabled for failover.
+ *
+ * Additionally, it drops slots that are valid on the primary but got
+ * invalidated on the standby. This situation may occur due to the following
+ * reasons:
+ * - The max_slot_wal_keep_size on the standby is insufficient to retain WAL
+ *   records from the restart_lsn of the slot.
+ * - primary_slot_name is temporarily reset to null and the physical slot is
+ *   removed.
+ * - The primary changes wal_level to a level lower than logical.
+ *
+ * The assumption is that these dropped local invalidated slots will get
+ * recreated in next sync-cycle and it is okay to drop and recreate such slots
+ * as long as these are not consumable on the standby (which is the case
+ * currently).
+ */
+static void
+drop_obsolete_slots(List *remote_slot_list)
+{
+	List	   *local_slots = NIL;
+	ListCell   *lc;
+
+	local_slots = get_local_synced_slots();
+
+	foreach(lc, local_slots)
+	{
+		ReplicationSlot *local_slot = (ReplicationSlot *) lfirst(lc);
+		bool		remote_exists = false;
+		bool		locally_invalidated = false;
+
+		remote_exists = check_sync_slot_on_remote(local_slot, remote_slot_list,
+												  &locally_invalidated);
+
+		/*
+		 * Drop the local slot either if it is not in the remote slots list or
+		 * is invalidated while remote slot is still valid.
+		 */
+		if (!remote_exists || locally_invalidated)
+		{
+			ReplicationSlotDrop(NameStr(local_slot->data.name), true, false);
+
+			ereport(LOG,
+					(errmsg("dropped replication slot \"%s\" of dbid %d",
+							NameStr(local_slot->data.name),
+							local_slot->data.database)));
+		}
+	}
+}
+
+/*
+ * Synchronize single slot to given position.
+ *
+ * This creates a new slot if there is no existing one and updates the
+ * metadata of the slot as per the data received from the primary server.
+ *
+ * The 'sync_state' in slot.data is set to SYNCSLOT_STATE_INITIATED
+ * immediately after creation. It stays in same state until the
+ * initialization is complete. The initialization is considered to
+ * be completed once the remote_slot catches up with locally reserved
+ * position and local slot is updated. The sync_state is then changed
+ * to SYNCSLOT_STATE_READY.
+ */
+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+					 bool *slot_updated)
+{
+	ReplicationSlot *s;
+	ReplicationSlot *slot;
+	char		sync_state = '\0';
+
+	/*
+	 * Sanity check: Make sure that concerned WAL is received before syncing
+	 * slot to target lsn received from the primary server.
+	 *
+	 * This check should never pass as on the primary server, we have waited
+	 * for the standby's confirmation before updating the logical slot.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (remote_slot->confirmed_lsn > WalRcv->latestWalEnd)
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		elog(ERROR, "skipping sync of slot \"%s\" as the received slot sync "
+			 "LSN %X/%X is ahead of the standby position %X/%X",
+			 remote_slot->name,
+			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
+			 LSN_FORMAT_ARGS(WalRcv->latestWalEnd));
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* Search for the named slot */
+	if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+	{
+		SpinLockAcquire(&s->mutex);
+		sync_state = s->data.sync_state;
+		SpinLockRelease(&s->mutex);
+
+		/* User created slot with the same name exists, raise ERROR. */
+		if (sync_state == SYNCSLOT_STATE_NONE)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("skipping sync of slot \"%s\" as it is a user created"
+							" slot", remote_slot->name),
+					 errdetail("This slot has failover enabled on the primary and"
+							   " thus is sync candidate but user created slot with"
+							   " the same name already exists on the standby")));
+		}
+	}
+
+	StartTransactionCommand();
+
+	/* Slot created by the slot sync worker exists, sync it */
+	if (sync_state)
+	{
+		Assert(sync_state == SYNCSLOT_STATE_READY ||
+			   sync_state == SYNCSLOT_STATE_INITIATED);
+
+		ReplicationSlotAcquire(remote_slot->name, true);
+
+		slot = MyReplicationSlot;
+
+		/*
+		 * Copy the invalidation cause from remote only if local slot is not
+		 * invalidated locally, we don't want to overwrite existing one.
+		 */
+		if (slot->data.invalidated == RS_INVAL_NONE)
+		{
+			SpinLockAcquire(&slot->mutex);
+			slot->data.invalidated = remote_slot->invalidated;
+			SpinLockRelease(&slot->mutex);
+		}
+
+		/* Skip the sync if slot has been invalidated locally. */
+		if (slot->data.invalidated != RS_INVAL_NONE)
+			goto cleanup;
+
+		/* Slot ready for sync, so sync it. */
+		if (sync_state == SYNCSLOT_STATE_READY)
+		{
+			/*
+			 * Sanity check: With hot_standby_feedback enabled and
+			 * invalidations handled apropriately as above, this should never
+			 * happen.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn)
+			{
+				elog(ERROR,
+					 "not synchronizing local slot \"%s\" LSN(%X/%X)"
+					 " to remote slot's LSN(%X/%X) as synchronization "
+					 " would move it backwards", remote_slot->name,
+					 LSN_FORMAT_ARGS(slot->data.restart_lsn),
+					 LSN_FORMAT_ARGS(remote_slot->restart_lsn));
+			}
+
+			if (remote_slot->confirmed_lsn != slot->data.confirmed_flush ||
+				remote_slot->restart_lsn != slot->data.restart_lsn ||
+				remote_slot->catalog_xmin != slot->data.catalog_xmin)
+			{
+				/* Update LSN of slot to remote slot's current position */
+				local_slot_update(remote_slot);
+				ReplicationSlotSave();
+				*slot_updated = true;
+			}
+		}
+
+		/* Slot not ready yet, let's attempt to make it sync-ready now. */
+		else if (sync_state == SYNCSLOT_STATE_INITIATED)
+		{
+			/*
+			 * Wait for the primary server to catch-up. Refer to the comment
+			 * atop the file for details on this wait.
+			 */
+			if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+				TransactionIdPrecedes(remote_slot->catalog_xmin,
+									  slot->data.catalog_xmin))
+			{
+				if (!wait_for_primary_slot_catchup(wrconn, remote_slot, NULL))
+					goto cleanup;
+			}
+
+			/*
+			 * Wait for primary is over, update the lsns and mark the slot as
+			 * READY for further syncs.
+			 */
+			local_slot_update(remote_slot);
+			SpinLockAcquire(&slot->mutex);
+			slot->data.sync_state = SYNCSLOT_STATE_READY;
+			SpinLockRelease(&slot->mutex);
+
+			/* Save the changes */
+			ReplicationSlotMarkDirty();
+			ReplicationSlotSave();
+			*slot_updated = true;
+
+			ereport(LOG,
+					errmsg("newly locally created slot \"%s\" is sync-ready now",
+						   remote_slot->name));
+		}
+	}
+	/* Otherwise create the slot first. */
+	else
+	{
+		TransactionId xmin_horizon = InvalidTransactionId;
+
+		ReplicationSlotCreate(remote_slot->name, true, RS_EPHEMERAL,
+							  remote_slot->two_phase,
+							  remote_slot->failover,
+							  SYNCSLOT_STATE_INITIATED);
+
+		slot = MyReplicationSlot;
+
+		SpinLockAcquire(&slot->mutex);
+		slot->data.database = get_database_oid(remote_slot->database, false);
+		namestrcpy(&slot->data.plugin, remote_slot->plugin);
+		SpinLockRelease(&slot->mutex);
+
+		ReplicationSlotReserveWal();
+
+		LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+		xmin_horizon = GetOldestSafeDecodingTransactionId(true);
+		SpinLockAcquire(&slot->mutex);
+		slot->effective_catalog_xmin = xmin_horizon;
+		slot->data.catalog_xmin = xmin_horizon;
+		SpinLockRelease(&slot->mutex);
+		ReplicationSlotsComputeRequiredXmin(true);
+		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * Wait for the primary server to catch-up. Refer to the comment atop
+		 * the file for details on this wait.
+		 */
+		if (remote_slot->restart_lsn < slot->data.restart_lsn ||
+			TransactionIdPrecedes(remote_slot->catalog_xmin,
+								  slot->data.catalog_xmin))
+		{
+			bool		wait_attempts_exceeded = false;
+
+			if (!wait_for_primary_slot_catchup(wrconn, remote_slot, &wait_attempts_exceeded))
+			{
+				/*
+				 * The remote slot didn't catch up to locally reserved
+				 * position.
+				 *
+				 * We do not drop the slot because the restart_lsn can be
+				 * ahead of the current location when recreating the slot in
+				 * the next cycle. It may take more time to create such a
+				 * slot. Therefore, we persist it (provided remote-slot is
+				 * still valid) and attempt the wait and synchronization in
+				 * the next cycle.
+				 */
+				if (wait_attempts_exceeded)
+				{
+					ReplicationSlotPersist();
+					*slot_updated = true;
+				}
+
+				goto cleanup;
+			}
+		}
+
+		/*
+		 * Wait for primary is either not needed or is over. Update the lsns
+		 * and mark the slot as READY for further syncs.
+		 */
+		local_slot_update(remote_slot);
+		SpinLockAcquire(&slot->mutex);
+		slot->data.sync_state = SYNCSLOT_STATE_READY;
+		SpinLockRelease(&slot->mutex);
+
+		/* Mark the slot as PERSISTENT and save the changes to disk */
+		ReplicationSlotPersist();
+		*slot_updated = true;
+
+		ereport(LOG,
+				errmsg("newly locally created slot \"%s\" is sync-ready now",
+					   remote_slot->name));
+	}
+
+cleanup:
+
+	ReplicationSlotRelease();
+	CommitTransactionCommand();
+
+	return;
+}
+
+/*
+ * Synchronize slots.
+ *
+ * Gets the failover logical slots info from the primary server and updates
+ * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns TRUE if any of the slots gets updated in this sync-cycle.
+ */
+static bool
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+	Oid			slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+	LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+	WalRcvExecResult *res;
+	TupleTableSlot *tupslot;
+	StringInfoData s;
+	List	   *remote_slot_list = NIL;
+	MemoryContext oldctx = CurrentMemoryContext;
+	ListCell   *lc;
+	bool		slot_updated = false;
+
+	/*
+	 * The primary_slot_name is not set yet or WALs not received yet.
+	 * Synchronization is not possible if the walreceiver is not started.
+	 */
+	SpinLockAcquire(&WalRcv->mutex);
+	if (!WalRcv ||
+		(WalRcv->slotname[0] == '\0') ||
+		XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+	{
+		SpinLockRelease(&WalRcv->mutex);
+		return slot_updated;
+	}
+	SpinLockRelease(&WalRcv->mutex);
+
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	StartTransactionCommand();
+
+	/*
+	 * Make result tuples live outside TopTransactionContext to make them
+	 * accessible even after transaction is committed.
+	 */
+	MemoryContextSwitchTo(oldctx);
+
+	initStringInfo(&s);
+
+	/*
+	 * Construct query to fetch slots with failover enabled.
+	 *
+	 * If we are on cascading standby, we should fetch only those slots from
+	 * the first standby which have sync_state as either 'n' or 'r'. Slots
+	 * with sync_state as 'i' are not sync ready yet. And when we are on the
+	 * first standby, the primary server is supposed to have slots with
+	 * sync_state as 'n' only (or it may have all 'n', 'r' and 'i' if standby
+	 * is promoted as primary). Thus in all the cases, filter sync_state !='i'
+	 * is appropriate one.
+	 */
+	appendStringInfo(&s,
+					 "SELECT slot_name, plugin, confirmed_flush_lsn,"
+					 " restart_lsn, catalog_xmin, two_phase, failover,"
+					 " database, pg_get_slot_invalidation_cause(slot_name)"
+					 " FROM pg_catalog.pg_replication_slots"
+					 " WHERE failover and sync_state != 'i'");
+
+	elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+	/* Execute the query */
+	res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+	pfree(s.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch failover logical slots info "
+						"from the primary server: %s", res->err)));
+
+	CommitTransactionCommand();
+
+	/* Switch to oldctx we saved */
+	MemoryContextSwitchTo(oldctx);
+
+	/* Construct the remote_slot tuple and synchronize each slot locally */
+	tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	while (tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
+	{
+		bool		isnull;
+		RemoteSlot *remote_slot = palloc0(sizeof(RemoteSlot));
+
+		remote_slot->name = TextDatumGetCString(slot_getattr(tupslot, 1, &isnull));
+		Assert(!isnull);
+
+		remote_slot->plugin = TextDatumGetCString(slot_getattr(tupslot, 2, &isnull));
+		Assert(!isnull);
+
+		/*
+		 * It is possible to get null values for LSN and Xmin if slot is
+		 * invalidated on the primary server, so handle accordingly.
+		 */
+		remote_slot->confirmed_lsn = DatumGetLSN(slot_getattr(tupslot, 3, &isnull));
+		if (isnull)
+			remote_slot->confirmed_lsn = InvalidXLogRecPtr;
+
+		remote_slot->restart_lsn = DatumGetLSN(slot_getattr(tupslot, 4, &isnull));
+		if (isnull)
+			remote_slot->restart_lsn = InvalidXLogRecPtr;
+
+		remote_slot->catalog_xmin = DatumGetTransactionId(slot_getattr(tupslot, 5,
+																	   &isnull));
+		if (isnull)
+			remote_slot->catalog_xmin = InvalidTransactionId;
+
+		remote_slot->two_phase = DatumGetBool(slot_getattr(tupslot, 6, &isnull));
+		Assert(!isnull);
+
+		remote_slot->failover = DatumGetBool(slot_getattr(tupslot, 7, &isnull));
+		Assert(!isnull);
+
+		remote_slot->database = TextDatumGetCString(slot_getattr(tupslot,
+																 8, &isnull));
+		Assert(!isnull);
+
+		remote_slot->invalidated = DatumGetInt16(slot_getattr(tupslot, 9, &isnull));
+		Assert(!isnull);
+
+		/* Create list of remote slots */
+		remote_slot_list = lappend(remote_slot_list, remote_slot);
+
+		ExecClearTuple(tupslot);
+	}
+
+	/*
+	 * Drop local slots that no longer need to be synced. Do it before
+	 * synchronize_one_slot to allow dropping of slots before actual sync
+	 * which are invalidated locally while still valid on the primary server.
+	 */
+	drop_obsolete_slots(remote_slot_list);
+
+	/* Now sync the slots locally */
+	foreach(lc, remote_slot_list)
+	{
+		RemoteSlot *remote_slot = (RemoteSlot *) lfirst(lc);
+
+		synchronize_one_slot(wrconn, remote_slot, &slot_updated);
+	}
+
+
+
+	/* We are done, free remote_slot_list elements */
+	list_free_deep(remote_slot_list);
+
+	walrcv_clear_result(res);
+
+	return slot_updated;
+}
+
+/*
+ * Connect to the remote (primary) server.
+ *
+ * This uses GUC primary_conninfo in order to connect to the primary.
+ * For slot sync to work, primary_conninfo is required to specify dbname
+ * as well.
+ */
+static WalReceiverConn *
+remote_connect(void)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *err;
+
+	wrconn = walrcv_connect(PrimaryConnInfo, true, false,
+							cluster_name[0] ? cluster_name : "slotsyncworker",
+							&err);
+	if (wrconn == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_CONNECTION_FAILURE),
+				 errmsg("could not connect to the primary server: %s", err)));
+	return wrconn;
+}
+
+/*
+ * Using the specified primary server connection, validate if the
+ * physical slot identified by GUC primary_slot_name exists.
+ *
+ * Exit the worker if the slot is not found.
+ */
+static void
+validate_primary_slot(WalReceiverConn *wrconn)
+{
+	WalRcvExecResult *res;
+	Oid			slotRow[1] = {BOOLOID};
+	StringInfoData cmd;
+	bool		isnull;
+	TupleTableSlot *slot;
+	bool		valid;
+	bool		tuple_ok;
+
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	StartTransactionCommand();
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd,
+					 "SELECT count(*) = 1 from pg_replication_slots "
+					 "WHERE slot_type='physical' and slot_name=%s",
+					 quote_literal_cstr(PrimarySlotName));
+
+	res = walrcv_exec(wrconn, cmd.data, 1, slotRow);
+	pfree(cmd.data);
+
+	if (res->status != WALRCV_OK_TUPLES)
+		ereport(ERROR,
+				(errmsg("could not fetch primary_slot_name \"%s\" info from the "
+						"primary: %s", PrimarySlotName, res->err)));
+
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+	tuple_ok = tuplestore_gettupleslot(res->tuplestore, true, false, slot);
+	Assert(tuple_ok);			/* It must return one tuple */
+
+	valid = DatumGetBool(slot_getattr(slot, 1, &isnull));
+	Assert(!isnull);
+
+	if (!valid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("exiting from slot synchronization due to bad configuration"),
+		/* translator: second %s is a GUC variable name */
+				 errdetail("The primary slot \"%s\" specified by %s is not valid.",
+						   PrimarySlotName, "primary_slot_name")));
+	ExecClearTuple(slot);
+	walrcv_clear_result(res);
+	CommitTransactionCommand();
+}
+
+/*
+ * Checks if GUCs are set appropriately before starting slot sync worker
+ *
+ * The slot sync worker can not start if 'enable_syncslot' is off and
+ * since 'enable_syncslot' is ON, check that the other GUC settings
+ * (primary_slot_name, hot_standby_feedback, wal_level, primary_conninfo)
+ * are compatible with slot synchronization. If not, raise ERROR.
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{
+	/*
+	 * A physical replication slot(primary_slot_name) is required on the
+	 * primary to ensure that the rows needed by the standby are not removed
+	 * after restarting, so that the synchronized slot on the standby will not
+	 * be invalidated.
+	 */
+	if (PrimarySlotName == NULL || strcmp(PrimarySlotName, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_slot_name"));
+
+	/*
+	 * Hot_standby_feedback must be enabled to cooperate with the physical
+	 * replication slot, which allows informing the primary about the xmin and
+	 * catalog_xmin values on the standby.
+	 */
+	if (!hot_standby_feedback)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be enabled.", "hot_standby_feedback"));
+
+	/*
+	 * Logical decoding requires wal_level >= logical and we currently only
+	 * synchronize logical slots.
+	 */
+	if (wal_level < WAL_LEVEL_LOGICAL)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("wal_level must be >= logical."));
+
+	/*
+	 * The primary_conninfo is required to make connection to primary for
+	 * getting slots information.
+	 */
+	if (PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0)
+		ereport(ERROR,
+		/* translator: %s is a GUC variable name */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("%s must be defined.", "primary_conninfo"));
+
+	/*
+	 * The slot sync worker needs a database connection for walrcv_exec to
+	 * work.
+	 */
+	*dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	if (*dbname == NULL)
+		ereport(ERROR,
+
+		/*
+		 * translator: 'dbname' is a specific option; %s is a GUC variable
+		 * name
+		 */
+				errmsg("exiting from slot synchronization due to bad configuration"),
+				errhint("'dbname' must be specified in %s.", "primary_conninfo"));
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs have changed, re-validate them. The
+ * worker will exit if the check fails.
+ */
+static void
+slotsync_reread_config(WalReceiverConn *wrconn)
+{
+	char	   *old_primary_conninfo = pstrdup(PrimaryConnInfo);
+	char	   *old_primary_slot_name = pstrdup(PrimarySlotName);
+	bool		old_hot_standby_feedback = hot_standby_feedback;
+	bool		revalidate = false;
+	char	   *old_dbname;
+	bool		conninfoChanged;
+	bool		slotnameChanged;
+
+	old_dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	Assert(old_dbname);
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	conninfoChanged = strcmp(old_primary_conninfo, PrimaryConnInfo) != 0;
+	slotnameChanged = strcmp(old_primary_slot_name, PrimarySlotName) != 0;
+
+	revalidate = conninfoChanged || slotnameChanged ||
+		(old_hot_standby_feedback != hot_standby_feedback);
+
+	pfree(old_primary_conninfo);
+	pfree(old_primary_slot_name);
+
+	if (revalidate)
+	{
+		char	   *new_dbname;
+
+		validate_slotsync_parameters(&new_dbname);
+
+		/*
+		 * Since we have initialized this worker with the old dbname, thus
+		 * exit if dbname changed. Let it get restarted and connect to the new
+		 * dbname specified.
+		 */
+		if (conninfoChanged && strcmp(old_dbname, new_dbname) != 0)
+			ereport(ERROR,
+					errmsg("exiting slot sync worker as dbname in "
+						   "primary_conninfo changed"));
+
+		if (slotnameChanged)
+			validate_primary_slot(wrconn);
+	}
+}
+
+/*
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting"
+					   " down on receiving SIGINT"));
+
+		walrcv_disconnect(wrconn);
+		proc_exit(0);
+	}
+
+	if (ConfigReloadPending)
+		slotsync_reread_config(wrconn);
+}
+
+/*
+ * Cleanup function for logical replication launcher.
+ *
+ * Called on logical replication launcher exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = InvalidPid;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+void
+ReplSlotSyncWorkerMain(Datum main_arg)
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	Assert(SlotSyncWorker->pid == InvalidPid);
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	validate_slotsync_parameters(&dbname);
+
+	/*
+	 * Connect to the database specified by user in primary_conninfo. We need
+	 * a database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	BackgroundWorkerInitializeConnection(dbname, NULL, 0);
+
+	/* Connect to the primary server */
+	wrconn = remote_connect();
+
+	/*
+	 * Using the existing primary server connection, validate the slot
+	 * specified in primary_slot_name.
+	 */
+	validate_primary_slot(wrconn);
+
+	/* Main wait loop. */
+	for (;;)
+	{
+		int			rc;
+		long		naptime = WORKER_DEFAULT_NAPTIME_MS;
+		TimestampTz now;
+		bool		slot_updated;
+
+		ProcessSlotSyncInterrupts(wrconn);
+
+		slot_updated = synchronize_slots(wrconn);
+
+		/*
+		 * If any of the slots get updated in this sync-cycle, use default
+		 * naptime and update 'last_update_time'. But if no activity is
+		 * observed in this sync-cycle, then increase naptime provided
+		 * inactivity time reaches threshold.
+		 */
+		now = GetCurrentTimestamp();
+		if (slot_updated)
+			last_update_time = now;
+		else if (TimestampDifferenceExceeds(last_update_time,
+											now, WORKER_INACTIVITY_THRESHOLD_MS))
+			naptime = WORKER_INACTIVITY_NAPTIME_MS;
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   naptime,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the logical replication launcher, or when there
+	 * is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Is current process the slot sync worker?
+ */
+bool
+IsLogicalSlotSyncWorker(void)
+{
+	return SlotSyncWorker->pid == MyProcPid;
+}
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (SlotSyncWorker->pid == InvalidPid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	/* Wait for it to die. */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long. */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (SlotSyncWorker->pid == InvalidPid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Allocate and initialize slot sync worker shared memory
+ */
+void
+SlotSyncWorkerShmemInit(void)
+{
+	Size		size;
+	bool		found;
+
+	size = sizeof(SlotSyncWorkerCtx);
+	size = MAXALIGN(size);
+
+	SlotSyncWorker = (SlotSyncWorkerCtx *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
+
+	if (!found)
+	{
+		memset(SlotSyncWorker, 0, size);
+		SlotSyncWorker->pid = InvalidPid;
+		SpinLockInit(&SlotSyncWorker->mutex);
+	}
+}
+
+/*
+ * Register the background worker for slots synchronization provided
+ * enable_syncslot is ON.
+ */
+void
+SlotSyncWorkerRegister(void)
+{
+	BackgroundWorker bgw;
+
+	if (!enable_syncslot)
+	{
+		ereport(LOG,
+				errmsg("skipping slots synchronization because enable_syncslot is "
+					   "disabled."));
+		return;
+	}
+
+	memset(&bgw, 0, sizeof(bgw));
+
+	/* We need database connection which needs shared-memory access as well. */
+	bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+
+	/* Start as soon as a consistent state has been reached in a hot standby */
+	bgw.bgw_start_time = BgWorkerStart_ConsistentState_HotStandby;
+
+	snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+	snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ReplSlotSyncWorkerMain");
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "replication slot sync worker");
+	snprintf(bgw.bgw_type, BGW_MAXLEN,
+			 "slot sync worker");
+
+	bgw.bgw_restart_time = BGW_DEFAULT_RESTART_INTERVAL;
+	bgw.bgw_notify_pid = 0;
+	bgw.bgw_main_arg = (Datum) 0;
+
+	RegisterBackgroundWorker(&bgw);
+}
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 7b6170fe55..64fcfdac9a 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "commands/subscriptioncmds.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index e46a1955e8..7b3784c212 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -141,7 +141,20 @@
  * subscribe to the new primary without losing any data.
  *
  * However, we do not enable failover for slots created by the table sync
- * worker.
+ * worker. This is because the table sync slot might not be fully synced on the
+ * standby due to the following reasons:
+ *
+ * - The standby needs to wait for the primary server to catch up because the
+ *   local restart_lsn of the newly created slot on the standby is set using
+ *   the latest redo position (GetXLogReplayRecPtr()), which is typically ahead
+ *   of the primary's restart_lsn.
+ * - The table sync slot's restart_lsn won't be advanced until the state
+ *   becomes SUBREL_STATE_CATCHUP.
+ *
+ * Therefore, if a failover happens before the restart_lsn advances, the table
+ * sync slot will not be synced to the standby. Consequently, we will not be
+ * able to subscribe to the promoted standby due to the absence of the
+ * necessary table sync slot.
  *
  * Additionally, failover is not enabled for the main slot if the table sync is
  * in progress. This is because if a failover occurs while the table sync
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index d5e729b2eb..67505cca82 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
+#include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -262,11 +263,14 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     user will only get commit prepared.
  * failover: Allows the slot to be synced to physical standbys so that logical
  *     replication can be resumed after failover.
+ * sync_state: Defines slot synchronization state. For user created slots, it
+ * is SYNCSLOT_STATE_NONE and for the slots being synchronized on the physical
+ * standby, it is either SYNCSLOT_STATE_INITIATED or SYNCSLOT_STATE_READY
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
 					  ReplicationSlotPersistency persistency,
-					  bool two_phase, bool failover)
+					  bool two_phase, bool failover, char sync_state)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
@@ -327,6 +331,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	slot->data.two_phase = two_phase;
 	slot->data.two_phase_at = InvalidXLogRecPtr;
 	slot->data.failover = failover;
+	slot->data.sync_state = sync_state;
 
 	/* and then data only present in shared memory */
 	slot->just_dirtied = false;
@@ -686,12 +691,26 @@ restart:
  * Permanently drop replication slot identified by the passed in name.
  */
 void
-ReplicationSlotDrop(const char *name, bool nowait)
+ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd)
 {
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name, nowait);
 
+	/*
+	 * Do not allow users to drop the slots which are currently being synced
+	 * from the primary to the standby.
+	 */
+	if (user_cmd && RecoveryInProgress() &&
+		MyReplicationSlot->data.sync_state != SYNCSLOT_STATE_NONE)
+	{
+		ReplicationSlotRelease();
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot drop replication slot \"%s\"", name),
+				 errdetail("This slot is being synced from the primary.")));
+	}
+
 	ReplicationSlotDropAcquired();
 }
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index c87f61666d..e5ba5956aa 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -44,7 +44,7 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 	/* acquire replication slot, this will check for conflicting names */
 	ReplicationSlotCreate(name, false,
 						  temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
-						  false);
+						  false, SYNCSLOT_STATE_NONE);
 
 	if (immediately_reserve)
 	{
@@ -137,7 +137,7 @@ create_logical_replication_slot(char *name, char *plugin,
 	 */
 	ReplicationSlotCreate(name, true,
 						  temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
-						  failover);
+						  failover, SYNCSLOT_STATE_NONE);
 
 	/*
 	 * Create logical decoding context to find start point or, if we don't
@@ -226,11 +226,38 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 
 	CheckSlotRequirements();
 
-	ReplicationSlotDrop(NameStr(*name), true);
+	ReplicationSlotDrop(NameStr(*name), true, true);
 
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * returns NULL if slot with given name is not found.
+ *
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+
+	if (s == NULL)
+		PG_RETURN_NULL();
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
@@ -238,7 +265,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 16
+#define PG_GET_REPLICATION_SLOTS_COLS 17
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	XLogRecPtr	currlsn;
 	int			slotno;
@@ -420,6 +447,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 
 		values[i++] = BoolGetDatum(slot_contents.data.failover);
 
+		values[i++] = CharGetDatum(slot_contents.data.sync_state);
+
 		Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index b5493fcd69..6b65c3c7dc 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1071,7 +1071,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	{
 		ReplicationSlotCreate(cmd->slotname, false,
 							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-							  false, false);
+							  false, false, SYNCSLOT_STATE_NONE);
 
 		if (reserve_wal)
 		{
@@ -1102,7 +1102,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		 */
 		ReplicationSlotCreate(cmd->slotname, true,
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-							  two_phase, failover);
+							  two_phase, failover, SYNCSLOT_STATE_NONE);
 
 		/*
 		 * Do options check early so that we can bail before calling the
@@ -1254,7 +1254,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
-	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
+	ReplicationSlotDrop(cmd->slotname, !cmd->wait, true);
 }
 
 /*
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0e0ac22bdd..ae2daff87b 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -37,6 +37,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -339,6 +340,7 @@ CreateOrAttachShmemStructs(void)
 	WalRcvShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7298a187d1..d87020821c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3286,6 +3286,17 @@ ProcessInterrupts(void)
 			 */
 			proc_exit(1);
 		}
+		else if (IsLogicalSlotSyncWorker())
+		{
+			elog(DEBUG1,
+				"replication slot sync worker is shutting down due to administrator command");
+
+			/*
+			 * Slot sync worker can be stopped at any time.
+			 * Use exit status 1 so the background worker is restarted.
+			 */
+			proc_exit(1);
+		}
 		else if (IsBackgroundWorker)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index ede94a1ede..7eb735824c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_PRIMARY_CATCHUP	"Waiting for the primary to catch-up, in slot sync worker."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 4b776266a4..2b40d5db6e 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
+#include "replication/worker_internal.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
@@ -2021,6 +2022,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"enable_syncslot", PGC_POSTMASTER, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&enable_syncslot,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5d940b72cd..39224137e1 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -358,6 +358,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#enable_syncslot = off			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bbc03bb76b..82a11e4a31 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,14 +11095,18 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   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,bool,bool}',
-  proargmodes => '{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,conflicting,failover}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,bool,bool,char}',
+  proargmodes => '{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,conflicting,failover,sync_state}',
   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/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 214dc6c29e..75b4b2040d 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -17,6 +17,7 @@
 
 #include "catalog/objectaddress.h"
 #include "parser/parse_node.h"
+#include "replication/walreceiver.h"
 
 extern ObjectAddress CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
 										bool isTopLevel);
@@ -28,4 +29,7 @@ extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
 extern char defGetStreamingMode(DefElem *def);
 
+extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn,
+										char *slotname, bool missing_ok);
+
 #endif							/* SUBSCRIPTIONCMDS_H */
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index e90ff376a6..8559900b70 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -79,6 +79,7 @@ typedef enum
 	BgWorkerStart_PostmasterStart,
 	BgWorkerStart_ConsistentState,
 	BgWorkerStart_RecoveryFinished,
+	BgWorkerStart_ConsistentState_HotStandby,
 } BgWorkerStartTime;
 
 #define BGW_DEFAULT_RESTART_INTERVAL	60
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index bbd71d0b42..945d2608f6 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -22,6 +22,7 @@ extern void TablesyncWorkerMain(Datum main_arg);
 
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
+extern bool IsLogicalSlotSyncWorker(void);
 
 extern void HandleParallelApplyMessageInterrupt(void);
 extern void HandleParallelApplyMessages(void);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 5ddad69348..5a03f8c8bb 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -15,7 +15,6 @@
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "storage/spin.h"
-#include "replication/walreceiver.h"
 
 /*
  * Behaviour of replication slots, upon release or crash.
@@ -52,6 +51,14 @@ typedef enum ReplicationSlotInvalidationCause
 	RS_INVAL_WAL_LEVEL,
 } ReplicationSlotInvalidationCause;
 
+/* The possible values for 'sync_state' in ReplicationSlotPersistentData */
+#define SYNCSLOT_STATE_NONE          'n'	/* None for user created slots */
+#define SYNCSLOT_STATE_INITIATED     'i'	/* Sync initiated for the slot but
+											 * not completed yet, waiting for
+											 * the primary server to catch-up */
+#define SYNCSLOT_STATE_READY         'r'	/* Initialization complete, ready
+											 * to be synced further */
+
 /*
  * On-Disk data of a replication slot, preserved across restarts.
  */
@@ -112,6 +119,13 @@ typedef struct ReplicationSlotPersistentData
 	/* plugin name */
 	NameData	plugin;
 
+	/*
+	 * Is this a slot created by a sync-slot worker?
+	 *
+	 * Only relevant for logical slots on the physical standby.
+	 */
+	char		sync_state;
+
 	/*
 	 * Is this a failover slot (sync candidate for physical standbys)? Only
 	 * relevant for logical slots on the primary server.
@@ -225,9 +239,10 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  ReplicationSlotPersistency persistency,
-								  bool two_phase, bool failover);
+								  bool two_phase, bool failover,
+								  char sync_state);
 extern void ReplicationSlotPersist(void);
-extern void ReplicationSlotDrop(const char *name, bool nowait);
+extern void ReplicationSlotDrop(const char *name, bool nowait, bool user_cmd);
 extern void ReplicationSlotAlter(const char *name, bool failover);
 
 extern void ReplicationSlotAcquire(const char *name, bool nowait);
@@ -253,7 +268,6 @@ extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_l
 extern int	ReplicationSlotIndex(ReplicationSlot *slot);
 extern bool ReplicationSlotName(int index, Name name);
 extern void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot);
-extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok);
 
 extern void StartupReplicationSlots(void);
 extern void CheckPointReplicationSlots(bool is_shutdown);
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index f1135762fb..c96a814b26 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -19,6 +19,7 @@
 #include "pgtime.h"
 #include "port/atomics.h"
 #include "replication/logicalproto.h"
+#include "replication/slot.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/latch.h"
@@ -279,6 +280,21 @@ typedef void (*walrcv_get_senderinfo_fn) (WalReceiverConn *conn,
 typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
 											TimeLineID *primary_tli);
 
+/*
+ * walrcv_get_dbinfo_for_failover_slots_fn
+ *
+ * Run LIST_DBID_FOR_FAILOVER_SLOTS on primary server to get the
+ * list of unique DBIDs for failover logical slots
+ */
+typedef List *(*walrcv_get_dbinfo_for_failover_slots_fn) (WalReceiverConn *conn);
+
+/*
+ * walrcv_get_dbname_from_conninfo_fn
+ *
+ * Returns the dbid from the primary_conninfo
+ */
+typedef char *(*walrcv_get_dbname_from_conninfo_fn) (const char *conninfo);
+
 /*
  * walrcv_server_version_fn
  *
@@ -403,6 +419,7 @@ typedef struct WalReceiverFunctionsType
 	walrcv_get_conninfo_fn walrcv_get_conninfo;
 	walrcv_get_senderinfo_fn walrcv_get_senderinfo;
 	walrcv_identify_system_fn walrcv_identify_system;
+	walrcv_get_dbname_from_conninfo_fn walrcv_get_dbname_from_conninfo;
 	walrcv_server_version_fn walrcv_server_version;
 	walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile;
 	walrcv_startstreaming_fn walrcv_startstreaming;
@@ -428,6 +445,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
 	WalReceiverFunctions->walrcv_get_senderinfo(conn, sender_host, sender_port)
 #define walrcv_identify_system(conn, primary_tli) \
 	WalReceiverFunctions->walrcv_identify_system(conn, primary_tli)
+#define walrcv_get_dbname_from_conninfo(conninfo) \
+	WalReceiverFunctions->walrcv_get_dbname_from_conninfo(conninfo)
 #define walrcv_server_version(conn) \
 	WalReceiverFunctions->walrcv_server_version(conn)
 #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 84bb79ac0f..9406a2666f 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -237,6 +237,11 @@ extern PGDLLIMPORT bool in_remote_transaction;
 
 extern PGDLLIMPORT bool InitializingApplyWorker;
 
+/* Slot sync worker objects */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+extern PGDLLIMPORT bool enable_syncslot;
+
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid,
 												bool only_running);
@@ -326,6 +331,12 @@ extern void pa_decr_and_wait_stream_block(void);
 extern void pa_xact_finish(ParallelApplyWorkerInfo *winfo,
 						   XLogRecPtr remote_lsn);
 
+extern void ReplSlotSyncWorkerMain(Datum main_arg);
+extern void SlotSyncWorkerRegister(void);
+extern void ShutDownSlotSync(void);
+extern void slotsync_drop_initiated_slots(void);
+extern void SlotSyncWorkerShmemInit(void);
+
 #define isParallelApplyWorker(worker) ((worker)->in_use && \
 									   (worker)->type == WORKERTYPE_PARALLEL_APPLY)
 #define isTablesyncWorker(worker) ((worker)->in_use && \
diff --git a/src/test/recovery/t/050_standby_failover_slots_sync.pl b/src/test/recovery/t/050_standby_failover_slots_sync.pl
index 09b5d006a5..92c6837a1b 100644
--- a/src/test/recovery/t/050_standby_failover_slots_sync.pl
+++ b/src/test/recovery/t/050_standby_failover_slots_sync.pl
@@ -295,4 +295,213 @@ is( $primary->safe_psql(
 
 $subscriber1->safe_psql('postgres', "DROP SUBSCRIPTION regress_mysub3");
 
+# shutdown the standby and subscribers
+$subscriber1->stop;
+$subscriber2->stop;
+$standby1->stop;
+$standby2->stop;
+
+##################################################
+# Test logical failover slots on the standby
+# Configure standby3 to replicate and synchronize logical slots configured
+# for failover on the primary
+#
+#              failover slot lsub3_slot->| ----> subscriber3 (connected via logical replication)
+# primary --->                           |
+#              physical slot sb3_slot--->| ----> standby3 (connected via streaming replication)
+#                                        |                 lsub3_slot(synced_slot)
+##################################################
+
+# Cleanup old standby_slot_names
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = ''
+));
+$primary->start;
+
+$primary->psql('postgres',
+		q{SELECT pg_create_physical_replication_slot('sb3_slot');});
+
+# Create table and publication on primary
+$primary->safe_psql('postgres', "CREATE TABLE tab_mypub3 (a int PRIMARY KEY);");
+$primary->safe_psql('postgres', "CREATE PUBLICATION mypub3 FOR TABLE tab_mypub3;");
+
+$backup_name = 'backup3';
+$primary->backup($backup_name);
+
+# Create standby3
+my $standby3 = PostgreSQL::Test::Cluster->new('standby3');
+$standby3->init_from_backup(
+	$primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+
+my $connstr_1 = $primary->connstr;
+$standby3->stop;
+$standby3->append_conf(
+	'postgresql.conf', q{
+enable_syncslot = true
+hot_standby_feedback = on
+primary_slot_name = 'sb3_slot'
+});
+$standby3->append_conf(
+        'postgresql.conf', qq(
+primary_conninfo = '$connstr_1 dbname=postgres'
+));
+$standby3->start;
+
+# Add this standby into the primary's configuration
+$primary->stop;
+$primary->append_conf(
+	'postgresql.conf', qq(
+standby_slot_names = 'sb3_slot'
+));
+$primary->start;
+
+# Restart the standby
+$standby3->restart;
+my $standby3_conninfo = $standby3->connstr . ' dbname=postgres';
+
+# Create a subscriber node
+my $subscriber3 = PostgreSQL::Test::Cluster->new('subscriber3');
+$subscriber3->init(allows_streaming => 'logical');
+$subscriber3->start;
+$subscriber3->safe_psql('postgres', "CREATE TABLE tab_mypub3 (a int PRIMARY KEY);");
+
+# Create a subscription with failover = true & wait for sync to complete.
+$subscriber3->safe_psql('postgres',
+		"CREATE SUBSCRIPTION mysub3 CONNECTION '$publisher_connstr' "
+	  . "PUBLICATION mypub3 WITH (slot_name = lsub3_slot, failover = true);");
+$subscriber3->wait_for_subscription_sync;
+
+# Wait for the standby to start sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"lsub3_slot\"/,
+	$offset);
+
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+	"SELECT pg_log_standby_snapshot();");
+
+# Wait for the standby to finish sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? wait over for remote slot \"lsub3_slot\"/,
+	$offset);
+
+# Confirm that logical failover slot is created on the standby and is sync ready
+is($standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'lsub3_slot';}),
+	"t|r",
+	'logical slot has failover as true and sync_state as ready on standby');
+
+##################################################
+# Test to confirm that restart_lsn and confirmed_flush_lsn of the logical slot
+# on the primary is synced to the standby
+##################################################
+
+# Truncate table on primary
+$primary->safe_psql('postgres',
+	"TRUNCATE TABLE tab_mypub3;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+	"INSERT INTO tab_mypub3 SELECT generate_series(1, 10);");
+
+# Get the restart_lsn for the logical slot lsub3_slot on the primary
+my $primary_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';");
+
+# Confirm that restart_lsn of lsub3_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' >= restart_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';]);
+is($result, 't', 'restart_lsn of slot lsub3_slot synced to standby');
+
+# Get the confirmed_flush_lsn for the logical slot lsub3_slot on the primary
+$primary_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';");
+
+# Confirm that confirmed_flush_lsn of lsub3_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+	qq[SELECT '$primary_lsn' >= confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub3_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub3_slot synced to the standby');
+
+##################################################
+# Test that synchronized slot can neither be docoded nor dropped by the user
+##################################################
+
+# Disable hot_standby_feedback
+$standby3->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = off;');
+$standby3->restart;
+
+# Dropping of synced slot should result in error
+my ($result1, $stdout, $stderr) = $standby3->psql('postgres',
+	"SELECT pg_drop_replication_slot('lsub3_slot');");
+ok($stderr =~ /ERROR:  cannot drop replication slot "lsub3_slot"/,
+	"synced slot on standby cannot be dropped");
+
+# Logical decoding on synced slot should result in error
+($result1, $stdout, $stderr) = $standby3->psql('postgres',
+	"select * from pg_logical_slot_get_changes('lsub3_slot',NULL,NULL);");
+ok($stderr =~ /ERROR:  cannot use replication slot "lsub3_slot" for logical decoding/,
+	"logical decoding is not allowed on synced slot");
+
+# Enable hot_standby_feedback and restart standby
+$standby3->safe_psql('postgres', 'ALTER SYSTEM SET hot_standby_feedback = on;');
+$standby3->restart;
+
+##################################################
+# Create another slot which stays in sync_state as initiated ('i')
+##################################################
+
+# Create a logical slot with failover = true
+$primary->psql('postgres',
+		q{SELECT pg_create_logical_replication_slot('logical_slot','pgoutput', false, true, true);});
+
+# Wait for the standby to start sync
+$offset = -s $standby3->logfile;
+$standby3->wait_for_log(
+	qr/LOG: ( [A-Z0-9]+:)? waiting for remote slot \"logical_slot\"/,
+	$offset);
+
+# Confirm that the logical slot is created on the standby and is in sync initiated state
+  is($standby3->safe_psql('postgres',
+	q{SELECT failover, sync_state FROM pg_replication_slots WHERE slot_name = 'logical_slot';}),
+	"t|i",
+	'logical slot has failover as true and sync_state as initiated on standby');
+
+##################################################
+# Promote the standby3 to primary. Confirm that:
+# a) the sync-ready('r') slot 'lsub3_slot' is retained on new primary
+# b) the initiated('i') slot 'logical_slot'is dropped on promotion
+# c) logical replication for mysub3 is resumed succesfully after failover
+##################################################
+
+$standby3->promote;
+
+# Update subscription with new primary's connection info
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 DISABLE;");
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 CONNECTION '$standby3_conninfo';");
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 ENABLE;");
+
+is($standby3->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots WHERE slot_name in ('logical_slot','lsub3_slot');}),
+	'lsub3_slot',
+	'synced slot retained on new primary');
+
+# Insert data on the new primary
+$standby3->safe_psql('postgres',
+	"INSERT INTO tab_mypub3 SELECT generate_series(11, 20);");
+
+# Confirm that data in tab_mypub3 replicated on subscriber
+is( $subscriber3->safe_psql('postgres', q{SELECT count(*) FROM tab_mypub3;}),
+	"20",
+	'data replicated from new primary');
+
 done_testing();
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb3b04aa0c..f2e5a3849c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1474,8 +1474,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.safe_wal_size,
     l.two_phase,
     l.conflicting,
-    l.failover
-   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, conflicting, failover)
+    l.failover,
+    l.sync_state
+   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, conflicting, failover, sync_state)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf8..aac83755de 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -132,8 +132,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_removal       | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_syncslot                | off
  enable_tidscan                 | on
-(22 rows)
+(23 rows)
 
 -- There are always wait event descriptions for various types.
 select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 199863c6b5..10c6f32a30 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2321,6 +2321,7 @@ RelocationBufferInfo
 RelptrFreePageBtree
 RelptrFreePageManager
 RelptrFreePageSpanLeader
+RemoteSlot
 RenameStmt
 ReopenPtrType
 ReorderBuffer
@@ -2579,6 +2580,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
+SlotSyncWorkerCtx
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 08:17                                             ` Re: Synchronizing slots from primary to standby Dilip Kumar <[email protected]>
  2023-12-11 08:50                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-11 09:02                                                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-13 10:22                                                   ` Peter Smith <[email protected]>
  2023-12-14 05:38                                                     ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-15 05:39                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 2 replies; 113+ messages in thread

From: Peter Smith @ 2023-12-13 10:22 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Dilip Kumar <[email protected]>; Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi Shveta, here are some review comments for v45-0002.

======
doc/src/sgml/bgworker.sgml

1.
+   <variablelist>
+    <varlistentry>
+     <term><literal>BgWorkerStart_PostmasterStart</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_PostmasterStart</primary></indexterm>
+       Start as soon as postgres itself has finished its own initialization;
+       processes requesting this are not eligible for database connections.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState</primary></indexterm>
+       Start as soon as a consistent state has been reached in a hot-standby,
+       allowing processes to connect to databases and run read-only queries.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_RecoveryFinished</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_RecoveryFinished</primary></indexterm>
+       Start as soon as the system has entered normal read-write state. Note
+       that the <literal>BgWorkerStart_ConsistentState</literal> and
+      <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
+       in a server that's not a hot standby.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>BgWorkerStart_ConsistentState_HotStandby</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BgWorkerStart_ConsistentState_HotStandby</primary></indexterm>
+       Same meaning as <literal>BgWorkerStart_ConsistentState</literal> but
+       it is more strict in terms of the server i.e. start the worker only
+       if it is hot-standby.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>

Maybe reorder these slightly, because I felt it is better if the
BgWorkerStart_ConsistentState_HotStandby comes next after
BgWorkerStart_ConsistentState, which it refers to

For example::
1st.BgWorkerStart_PostmasterStart
2nd.BgWorkerStart_ConsistentState
3rd.BgWorkerStart_ConsistentState_HotStandby
4th.BgWorkerStart_RecoveryFinished

======
doc/src/sgml/config.sgml

2.
<varname>enable_syncslot</varname> = true

Not sure, but I thought the "= true" part should be formatted too.

SUGGESTION
<literal>enable_syncslot = true</literal>

======
doc/src/sgml/logicaldecoding.sgml

3.
+    <para>
+     A logical replication slot on the primary can be synchronized to the hot
+     standby by enabling the failover option during slot creation and setting
+     <varname>enable_syncslot</varname> on the standby. For the synchronization
+     to work, it is mandatory to have a physical replication slot between the
+     primary and the standby. It's highly recommended that the said physical
+     replication slot is listed in <varname>standby_slot_names</varname> on
+     the primary to prevent the subscriber from consuming changes faster than
+     the hot standby. Additionally, <varname>hot_standby_feedback</varname>
+     must be enabled on the standby for the slots synchronization to work.
+    </para>

I felt those parts that describe the mandatory GUCs should be kept together.

SUGGESTION
For the synchronization to work, it is mandatory to have a physical
replication slot between the primary and the standby, and
<varname>hot_standby_feedback</varname> must be enabled on the
standby.

It's also highly recommended that the said physical replication slot
is named in <varname>standby_slot_names</varname> list on the primary,
to prevent the subscriber from consuming changes faster than the hot
standby.

~~~

4. (Chapter 49)

By enabling synchronization of slots, logical replication can be
resumed after failover depending upon the
pg_replication_slots.sync_state for the synchronized slots on the
standby at the time of failover. Only slots that were in ready
sync_state ('r') on the standby before failover can be used for
logical replication after failover. However, the slots which were in
initiated sync_state ('i') and not sync-ready ('r') at the time of
failover will be dropped and logical replication for such slots can
not be resumed after failover. This applies to the case where a
logical subscription is disabled before failover and is enabled after
failover. If the synchronized slot due to disabled subscription could
not be made sync-ready ('r') on standby, then the subscription can not
be resumed after failover even when enabled. If the primary is idle,
then the synchronized slots on the standby may take a noticeable time
to reach the ready ('r') sync_state. This can be sped up by calling
the pg_log_standby_snapshot function on the primary.

~

Somehow, I still felt all that was too wordy/repetitive. Below is my
attempt to make it more concise. Thoughts?

SUGGESTION
The ability to resume logical replication after failover depends upon
the pg_replication_slots.sync_state value for the synchronized slots
on the standby at the time of failover. Only slots that have attained
a "ready" sync_state ('r') on the standby before failover can be used
for logical replication after failover. Slots that have not yet
reached 'r' state (they are still 'i') will be dropped, therefore
logical replication for those slots cannot be resumed. For example, if
the synchronized slot could not become sync-ready on standby due to a
disabled subscription, then the subscription cannot be resumed after
failover even when it is enabled.

If the primary is idle, the synchronized slots on the standby may take
a noticeable time to reach the ready ('r') sync_state. This can be
sped up by calling the pg_log_standby_snapshot function on the
primary.

======
doc/src/sgml/system-views.sgml

5.
+      <para>
+      Defines slot synchronization state. This is meaningful on the physical
+      standby which has configured <varname>enable_syncslot</varname> = true
+      </para>

As mentioned in the previous review comment ([1]#10) I thought it
might be good to include a hyperlink cross-reference to the
'enable_syncslot' GUC.

~~~

6.
+      <para>
+      The hot standby can have any of these sync_state for the slots but on a
+      hot standby, the slots with state 'r' and 'i' can neither be used for
+      logical decoding nor dropped by the user. The primary server will have
+      sync_state as 'n' for all the slots. But if the standby is promoted to
+      become the new primary server, sync_state can be seen 'r' as well. On
+      this new primary server, slots with sync_state as 'r' and 'n' will
+      behave the same.
+      </para></entry>

6a.
/these sync_state for the slots/these sync_state values for the slots/

~

6b
Hm. I still felt (same as previous review [1]#12b) that there seems
too much information here.

IIUC the sync_state is only meaningful on the standby. Sure, it might
have some values line 'n' or 'r' on the primary also, but those either
mean nothing ('n') or are leftover states from a previous failover
from a standby ('r'), which also means nothing. So can't we just say
it more succinctly like that?

SUGGESTION
The sync_state has no meaning on the primary server; the primary
sync_state value is default 'n' for all slots but may (if leftover
from a promoted standby)  also be 'r'.

======
.../libpqwalreceiver/libpqwalreceiver.c

7.
 static void
 libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
- bool failover)
+ bool failover)

Still seems to be tampering with indentation that should only be in patch 0001.

======
src/backend/replication/logical/slotsync.c

8. wait_for_primary_slot_catchup

The meaning of the boolean return of this function is still not
described by the function comment.

~~~

9.
+ * If passed, *wait_attempts_exceeded will be set to true only if this
+ * function exits after exhausting its wait attempts. It will be false
+ * in all the other cases like failure, remote-slot invalidation, primary
+ * could catch up.

The above already says when a return false happens, so it seems
overkill to give more information.

SUGGESTION
If passed, *wait_attempts_exceeded will be set to true only if this
function exits due to exhausting its wait attempts. It will be false
in all the other cases.

~~~

10.

+static bool
+wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+   bool *wait_attempts_exceeded)
+{
+#define WAIT_OUTPUT_COLUMN_COUNT 4
+#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
+

10a
Maybe the long constant name is too long. How about
WAIT_PRIMARY_CATCHUP_ATTEMPTS?

~~~

10b.
IMO it is better to Assert the input value of this kind of side-effect
return parameter, to give a better understanding and to prevent future
accidents.

SUGGESTION
Assert(wait_attempts_exceeded == NULL |} *wait_attempts_exceeded == false);

~~~

11. synchronize_one_slot

+ ReplicationSlot *s;
+ ReplicationSlot *slot;
+ char sync_state = '\0';

11a.
I don't think you need both 's' and 'slot' ReplicationSlot -- it looks
a bit odd. Can't you just reuse the one 'slot' variable?

~

11b.
Also, maybe those assignment like
+ slot = MyReplicationSlot;

can have an explanatory comment like:
/* For convenience, we assign MyReplicationSlot to a shorter variable name. */

~~~

12.
+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+ bool *slot_updated)
+{
+ ReplicationSlot *s;
+ ReplicationSlot *slot;
+ char sync_state = '\0';

In my previous review [1]#33a I thought it was strange to assign the
sync_state (which is essentially an enum) to some meaningless value,
so I suggested it should be set to SYNCSLOT_STATE_NONE in the
declaration. The reply [2] was "No, that will change the flow. It
should stay uninitialized if the slot is not found."

But I am not convinced there is any flow problem. Also,
SYNCSLOT_STATE_NONE seems the naturally correct default for something
with no state. It cannot be found and be SYNCSLOT_STATE_NONE at the
same time (that is reported as an ERROR "skipping sync of slot") so I
see no problem.

The CURRENT code is like this:

/* Slot created by the slot sync worker exists, sync it */
if (sync_state)
{
  Assert(sync_state == SYNCSLOT_STATE_READY || sync_state ==
SYNCSLOT_STATE_INITIATED);
  ...
}
/* Otherwise create the slot first. */
else
{
  ...
}

AFAICT that could easily be changed to like below, with no change to
the logic, and it avoids setting strange values.

SUGGESTION.

if (sync_state == SYNCSLOT_STATE_NONE)
{
  /* Slot not found. Create it. */
  ..
}
else
{
  Assert(sync_state == SYNCSLOT_STATE_READY || sync_state ==
SYNCSLOT_STATE_INITIATED);
  ...
}

~~~

13. synchronize_one_slot

+static void
+synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
+ bool *slot_updated)

This *slot_updated parameter looks dubious. It is used in a loop from
the caller to mean that ANY slot was updated -- e.g. maybe it is true
or false on entry to this function.

But, Instead of having some dependency between this function and the
caller, IMO it makes more sense if we would make this just a boolean
function in the first place (e.g. was updated? T/F)

Then the caller can also be written more easily like:

some_slot_updated |= synchronize_one_slot(wrconn, remote_slot);

~~~

14.
+ /* Search for the named slot */
+ if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+ {
+ SpinLockAcquire(&s->mutex);
+ sync_state = s->data.sync_state;
+ SpinLockRelease(&s->mutex);
+
+ /* User created slot with the same name exists, raise ERROR. */
+ if (sync_state == SYNCSLOT_STATE_NONE)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("skipping sync of slot \"%s\" as it is a user created"
+ " slot", remote_slot->name),
+ errdetail("This slot has failover enabled on the primary and"
+    " thus is sync candidate but user created slot with"
+    " the same name already exists on the standby")));
+ }
+ }


Extra curly brackets around the ereport are not needed.

~~~

15.
+ /*
+ * Sanity check: With hot_standby_feedback enabled and
+ * invalidations handled apropriately as above, this should never
+ * happen.
+ */
+ if (remote_slot->restart_lsn < slot->data.restart_lsn)
+ {
+ elog(ERROR,
+ "not synchronizing local slot \"%s\" LSN(%X/%X)"
+ " to remote slot's LSN(%X/%X) as synchronization "
+ " would move it backwards", remote_slot->name,
+ LSN_FORMAT_ARGS(slot->data.restart_lsn),
+ LSN_FORMAT_ARGS(remote_slot->restart_lsn));
+ }

15a.
/apropriately/appropriately/

~

15b.
Extra curly brackets around the elog are not needed.

~~~

16. synchronize_slots

+static bool
+synchronize_slots(WalReceiverConn *wrconn)
+{
+#define SLOTSYNC_COLUMN_COUNT 9
+ Oid slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
+ LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
+
+ WalRcvExecResult *res;
+ TupleTableSlot *tupslot;
+ StringInfoData s;
+ List    *remote_slot_list = NIL;
+ MemoryContext oldctx = CurrentMemoryContext;
+ ListCell   *lc;
+ bool slot_updated = false;

Suggest renaming 'slot_updated' to 'some_slot_updated' or
'update_occurred' etc because the current name makes it look like it
applies to a single slot, but it doesn't.

~~~

17.
+ SpinLockAcquire(&WalRcv->mutex);
+ if (!WalRcv ||
+ (WalRcv->slotname[0] == '\0') ||
+ XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
+ {
+ SpinLockRelease(&WalRcv->mutex);
+ return slot_updated;
+ }
+ SpinLockRelease(&WalRcv->mutex);

IMO "return false;" here is more clear than saying "return slot_updated;"

~~~

18.
+ appendStringInfo(&s,
+ "SELECT slot_name, plugin, confirmed_flush_lsn,"
+ " restart_lsn, catalog_xmin, two_phase, failover,"
+ " database, pg_get_slot_invalidation_cause(slot_name)"
+ " FROM pg_catalog.pg_replication_slots"
+ " WHERE failover and sync_state != 'i'");

18a.
/and/AND/

~

18b.
In the reply post (see [2]#32) Shveta said "I could not find quote_*
function for a character just like we have 'quote_literal_cstr' for
string". If you still want to use constant substitution instead of
just hardwired 'i' then why do even you need a quote_* function? I
thought the appendStringInfo uses a printf style format-string
internally, so I assumed it is possible to substitute the state char
directly using '%c'.

~~~

19.
+
+
+
+ /* We are done, free remote_slot_list elements */
+ list_free_deep(remote_slot_list);
+
+ walrcv_clear_result(res);
+
+ return slot_updated;
+}

Excessive blank lines.

~~~

20. validate_primary_slot

+ appendStringInfo(&cmd,
+ "SELECT count(*) = 1 from pg_replication_slots "
+ "WHERE slot_type='physical' and slot_name=%s",
+ quote_literal_cstr(PrimarySlotName));


/and/AND/

~~~

21.
+ slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
+ tuple_ok = tuplestore_gettupleslot(res->tuplestore, true, false, slot);
+ Assert(tuple_ok); /* It must return one tuple */

IMO it's better to use all the var names the same across all
functions? So call this 'tupslot' like the other
MakeSingleTupleTableSlot result.

~~~

22. validate_slotsync_parameters

+/*
+ * Checks if GUCs are set appropriately before starting slot sync worker
+ *
+ * The slot sync worker can not start if 'enable_syncslot' is off and
+ * since 'enable_syncslot' is ON, check that the other GUC settings
+ * (primary_slot_name, hot_standby_feedback, wal_level, primary_conninfo)
+ * are compatible with slot synchronization. If not, raise ERROR.
+ */
+static void
+validate_slotsync_parameters(char **dbname)
+{

22a.
The comment is quite verbose. IMO the 2nd para seems just unnecessary
detail of the 1st para.

SUGGESTION
Check that all necessary GUCs for slot synchronization are set
appropriately. If not, raise an ERROR.

~~~

22b.
IMO (and given what was said in the comment about enable_syncslot must
be on)the first statement of this function should be:

/* Sanity check. */
Assert(enable_syncslot);

~~~

23. slotsync_reread_config

+ old_dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+ Assert(old_dbname);

(This is same comment as old review [1]#61)

Hmm. I still don't see why this extraction of the dbname cannot be
deferred until later when you know the PrimaryConnInfo has changed,
otherwise, it might be redundant to do this. Shveta replied [2] that
"Once PrimaryConnInfo is changed, we can not get old-dbname.", but I'm
not so sure. Isn't this walrcv_get_dbname_from_conninfo just doing a
string search -- Why can't you defer this until you know
conninfoChanged is true, and then to get the old_dbname, you can just
pass the old_primary_conninfo. E.g. call like
walrcv_get_dbname_from_conninfo(old_primary_conninfo); Maybe I am
mistaken.

~~

24.
+ /*
+ * Since we have initialized this worker with the old dbname, thus
+ * exit if dbname changed. Let it get restarted and connect to the new
+ * dbname specified.
+ */
+ if (conninfoChanged && strcmp(old_dbname, new_dbname) != 0)
+ ereport(ERROR,
+ errmsg("exiting slot sync worker as dbname in "
+    "primary_conninfo changed"));

IIUC when the tablesync has to restart, it emits a LOG message before
it exits; but it's not an ERROR. So, shouldn't this be similar -- IMO
it is not an "error" for the user to wish to change the dbname. Maybe
this should be LOG followed by an explicit exit. If you agree, then it
might be better to encapsulate such logic in some little function:

// pseudo-code
void slotsync_worker_restart(const char *msg)
{
ereport(LOG, msg...
exit(0);
}

~~~

25. ReplSlotSyncWorkerMain

+ for (;;)
+ {
+ int rc;
+ long naptime = WORKER_DEFAULT_NAPTIME_MS;
+ TimestampTz now;
+ bool slot_updated;
+
+ ProcessSlotSyncInterrupts(wrconn);
+
+ slot_updated = synchronize_slots(wrconn);

Here I think the 'slot_updated' should be renamed to the same name as
in #16 above (e.g. 'some_slot_updated' or 'any_slot_updated' or
'update_occurred' etc).

~~~

26. SlotSyncWorkerRegister

+ if (!enable_syncslot)
+ {
+ ereport(LOG,
+ errmsg("skipping slots synchronization because enable_syncslot is "
+    "disabled."));
+ return;
+ }

Instead of saying "because..." in the error message maybe keep the
message more terse and describe the "because" part in the errdetail

SUGGESTION
errmsg("skipping slot synchronization")
errdetail("enable_syncslot is disabled.")


======
src/backend/replication/slot.c

27.
+ * sync_state: Defines slot synchronization state. For user created slots, it
+ * is SYNCSLOT_STATE_NONE and for the slots being synchronized on the physical
+ * standby, it is either SYNCSLOT_STATE_INITIATED or SYNCSLOT_STATE_READY
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
    ReplicationSlotPersistency persistency,
-   bool two_phase, bool failover)
+   bool two_phase, bool failover, char sync_state)


27a.
Why is this comment even mentioning SYNCSLOT_STATE_READY? IIUC it
doesn't make sense to ever call ReplicationSlotCreate directly setting
the 'r' state (e.g., bypassing 'i' ???)

~

27b.
Indeed, IMO there should be Assert(sync_state == SYNCSLOT_STATE_NONE
|| syncstate == SYNCSLOT_STATE_INITIATED); to guarantee this.

======
src/include/replication/slot.h

28.
+ /*
+ * Is this a slot created by a sync-slot worker?
+ *
+ * Only relevant for logical slots on the physical standby.
+ */
+ char sync_state;
+

(probably I am repeating a previous thought here)

The comment says the field is only relevant for standby, and that's
how I've been visualizing it, and why I had previously suggested even
renaming it to 'standby_sync_state'. However, replies are saying that
after failover these sync_states also have "some meaning for the
primary server".

That's the part I have trouble understanding. IIUC the server states
are just either all 'n' (means nothing) or 'r' because they are just
leftover from the old standby state. So, does it *truly* have meaning
for the server? Or should those states somehow be removed/ignored on
the new primary? Anyway, the point is that if this field does have
meaning also on the primary (I doubt) then those details should be in
this comment. Otherwise "Only relevant ... on the standby" is too
misleading.

======
.../t/050_standby_failover_slots_sync.pl

29.
+# Create table and publication on primary
+$primary->safe_psql('postgres', "CREATE TABLE tab_mypub3 (a int
PRIMARY KEY);");
+$primary->safe_psql('postgres', "CREATE PUBLICATION mypub3 FOR TABLE
tab_mypub3;");
+

29a.
/on primary/on the primary/

~

29b.
Consider to combine those DDL

~

29c.
Perhaps for consistency, you should be calling this 'regress_mypub3'.

~~~

30.
+# Create a subscriber node
+my $subscriber3 = PostgreSQL::Test::Cluster->new('subscriber3');
+$subscriber3->init(allows_streaming => 'logical');
+$subscriber3->start;
+$subscriber3->safe_psql('postgres', "CREATE TABLE tab_mypub3 (a int
PRIMARY KEY);");
+
+# Create a subscription with failover = true & wait for sync to complete.
+$subscriber3->safe_psql('postgres',
+ "CREATE SUBSCRIPTION mysub3 CONNECTION '$publisher_connstr' "
+   . "PUBLICATION mypub3 WITH (slot_name = lsub3_slot, failover = true);");
+$subscriber3->wait_for_subscription_sync;

30a
Consider combining those DDLs.

~

30b.
Probably for consistency, you should be calling this 'regress_mysub3'.

~~~

31.
+# Advance lsn on the primary
+$primary->safe_psql('postgres',
+ "SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+ "SELECT pg_log_standby_snapshot();");
+$primary->safe_psql('postgres',
+ "SELECT pg_log_standby_snapshot();");
+

Consider combining all those DDLs.

~~~

32.
+# Truncate table on primary
+$primary->safe_psql('postgres',
+ "TRUNCATE TABLE tab_mypub3;");
+
+# Insert data on the primary
+$primary->safe_psql('postgres',
+ "INSERT INTO tab_mypub3 SELECT generate_series(1, 10);");
+

Consider combining those DDLs.

~~~

33.
+# Confirm that restart_lsn of lsub3_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+ qq[SELECT '$primary_lsn' >= restart_lsn from pg_replication_slots
WHERE slot_name = 'lsub3_slot';]);
+is($result, 't', 'restart_lsn of slot lsub3_slot synced to standby');


Does "'$primary_lsn' >= restart_lsn" make sense here? NOTE, the sign
was '<=' in v43-0002

~~~

34.
+# Confirm that confirmed_flush_lsn of lsub3_slot slot is synced to the standby
+$result = $standby3->safe_psql('postgres',
+ qq[SELECT '$primary_lsn' >= confirmed_flush_lsn from
pg_replication_slots WHERE slot_name = 'lsub3_slot';]);
+is($result, 't', 'confirmed_flush_lsn of slot lsub3_slot synced to
the standby');

Does "'$primary_lsn' >= confirmed_flush_lsn" make sense here? NOTE,
the sign was '<=' in v43-0002

~~~

35.
+##################################################
+# Test that synchronized slot can neither be docoded nor dropped by the user
+##################################################

35a.
/docoded/decoded/

~

35b.
Please give explanation in the comment *why* those ops are not allowed
(e.g. because the hot_standby_feedback GUC does not have an accepted
value)

~~~

36.
+##################################################
+# Create another slot which stays in sync_state as initiated ('i')
+##################################################
+

Please explain the comment as to *why* it gets stuck in the initiated state.


~~~

37.
+##################################################
+# Promote the standby3 to primary. Confirm that:
+# a) the sync-ready('r') slot 'lsub3_slot' is retained on new primary
+# b) the initiated('i') slot 'logical_slot'is dropped on promotion
+# c) logical replication for mysub3 is resumed succesfully after failover
+##################################################


/'logical_slot'is/'logical_slot' is/ (missing space)

/succesfully/successfully/

~~~

38.
+# Update subscription with new primary's connection info
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 DISABLE;");
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3
CONNECTION '$standby3_conninfo';");
+$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 ENABLE;");


Consider combining all those DDLs.

~~~

39.
+
+# Insert data on the new primary
+$standby3->safe_psql('postgres',
+ "INSERT INTO tab_mypub3 SELECT generate_series(11, 20);");
+
+# Confirm that data in tab_mypub3 replicated on subscriber
+is( $subscriber3->safe_psql('postgres', q{SELECT count(*) FROM tab_mypub3;}),
+ "20",
+ 'data replicated from new primary');

Shouldn't there be some wait_for_subscription_sync logic (or similar)
here just to ensure the subscriber3 had time to receive that data
before you immediately check that it had arrived?

======
[1] My v43-0002 review.
https://www.postgresql.org/message-id/CAHut%2BPuuqEpDse5msENsVuK3rjTRN-QGS67rRCGVv%2BzcT-f0GA%40mail...
[2] Replies to v43-0002 review.
https://www.postgresql.org/message-id/CAJpy0uDcOf5Hvk_CdCCAbfx9SY%2Bog%3D%3D%3DtgiuhWKzkYyqebui9g%40...

Kind Regards,
Peter Smith.
Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 08:17                                             ` Re: Synchronizing slots from primary to standby Dilip Kumar <[email protected]>
  2023-12-11 08:50                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-11 09:02                                                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 10:22                                                   ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
@ 2023-12-14 05:38                                                     ` Amit Kapila <[email protected]>
  1 sibling, 0 replies; 113+ messages in thread

From: Amit Kapila @ 2023-12-14 05:38 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; +Cc: shveta malik <[email protected]>; Dilip Kumar <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Wed, Dec 13, 2023 at 3:53 PM Peter Smith <[email protected]> wrote:
>
> 12.
> +static void
> +synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
> + bool *slot_updated)
> +{
> + ReplicationSlot *s;
> + ReplicationSlot *slot;
> + char sync_state = '\0';
>
> In my previous review [1]#33a I thought it was strange to assign the
> sync_state (which is essentially an enum) to some meaningless value,
> so I suggested it should be set to SYNCSLOT_STATE_NONE in the
> declaration. The reply [2] was "No, that will change the flow. It
> should stay uninitialized if the slot is not found."
>
> But I am not convinced there is any flow problem. Also,
> SYNCSLOT_STATE_NONE seems the naturally correct default for something
> with no state. It cannot be found and be SYNCSLOT_STATE_NONE at the
> same time (that is reported as an ERROR "skipping sync of slot") so I
> see no problem.
>
> The CURRENT code is like this:
>
> /* Slot created by the slot sync worker exists, sync it */
> if (sync_state)
> {
>   Assert(sync_state == SYNCSLOT_STATE_READY || sync_state ==
> SYNCSLOT_STATE_INITIATED);
>   ...
> }
> /* Otherwise create the slot first. */
> else
> {
>   ...
> }
>
> AFAICT that could easily be changed to like below, with no change to
> the logic, and it avoids setting strange values.
>
> SUGGESTION.
>
> if (sync_state == SYNCSLOT_STATE_NONE)
> {
>   /* Slot not found. Create it. */
>   ..
> }
> else
> {
>   Assert(sync_state == SYNCSLOT_STATE_READY || sync_state ==
> SYNCSLOT_STATE_INITIATED);
>   ...
> }
>

I think instead of creating syncslot based on syncstate, it would be
better to create it when we don't find it via
SearchNamedReplicationSlot(). That will avoid the need to initialize
the syncstate and I think it would make code in this area look better.

> ~~~
>
> 13. synchronize_one_slot
>
> +static void
> +synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
> + bool *slot_updated)
>
> This *slot_updated parameter looks dubious. It is used in a loop from
> the caller to mean that ANY slot was updated -- e.g. maybe it is true
> or false on entry to this function.
>
> But, Instead of having some dependency between this function and the
> caller, IMO it makes more sense if we would make this just a boolean
> function in the first place (e.g. was updated? T/F)
>
> Then the caller can also be written more easily like:
>
> some_slot_updated |= synchronize_one_slot(wrconn, remote_slot);
>

+1.

>
> 23. slotsync_reread_config
>
> + old_dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
> + Assert(old_dbname);
>
> (This is same comment as old review [1]#61)
>
> Hmm. I still don't see why this extraction of the dbname cannot be
> deferred until later when you know the PrimaryConnInfo has changed,
> otherwise, it might be redundant to do this. Shveta replied [2] that
> "Once PrimaryConnInfo is changed, we can not get old-dbname.", but I'm
> not so sure. Isn't this walrcv_get_dbname_from_conninfo just doing a
> string search -- Why can't you defer this until you know
> conninfoChanged is true, and then to get the old_dbname, you can just
> pass the old_primary_conninfo. E.g. call like
> walrcv_get_dbname_from_conninfo(old_primary_conninfo); Maybe I am
> mistaken.
>

I think we should just restart if any one of the information is
changed with a message like: "slotsync worker will restart because of
a parameter change". This would be similar to what we do apply worker
in maybe_reread_subscription().

>
> 28.
> + /*
> + * Is this a slot created by a sync-slot worker?
> + *
> + * Only relevant for logical slots on the physical standby.
> + */
> + char sync_state;
> +
>
> (probably I am repeating a previous thought here)
>
> The comment says the field is only relevant for standby, and that's
> how I've been visualizing it, and why I had previously suggested even
> renaming it to 'standby_sync_state'. However, replies are saying that
> after failover these sync_states also have "some meaning for the
> primary server".
>
> That's the part I have trouble understanding. IIUC the server states
> are just either all 'n' (means nothing) or 'r' because they are just
> leftover from the old standby state. So, does it *truly* have meaning
> for the server? Or should those states somehow be removed/ignored on
> the new primary? Anyway, the point is that if this field does have
> meaning also on the primary (I doubt) then those details should be in
> this comment. Otherwise "Only relevant ... on the standby" is too
> misleading.
>

I think this deserves more comments.

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 08:17                                             ` Re: Synchronizing slots from primary to standby Dilip Kumar <[email protected]>
  2023-12-11 08:50                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-11 09:02                                                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-13 10:22                                                   ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
@ 2023-12-15 05:39                                                     ` shveta malik <[email protected]>
  1 sibling, 0 replies; 113+ messages in thread

From: shveta malik @ 2023-12-15 05:39 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; +Cc: Dilip Kumar <[email protected]>; Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Wed, Dec 13, 2023 at 3:53 PM Peter Smith <[email protected]> wrote:
>
> Hi Shveta, here are some review comments for v45-0002.
>

Thanks for the feedback. Addressed these in v48. Please find my
comments on some.

> ======
> doc/src/sgml/bgworker.sgml
>
> 1.
> +   <variablelist>
> +    <varlistentry>
> +     <term><literal>BgWorkerStart_PostmasterStart</literal></term>
> +     <listitem>
> +      <para>
> +       <indexterm><primary>BgWorkerStart_PostmasterStart</primary></indexterm>
> +       Start as soon as postgres itself has finished its own initialization;
> +       processes requesting this are not eligible for database connections.
> +      </para>
> +     </listitem>
> +    </varlistentry>
> +
> +    <varlistentry>
> +     <term><literal>BgWorkerStart_ConsistentState</literal></term>
> +     <listitem>
> +      <para>
> +       <indexterm><primary>BgWorkerStart_ConsistentState</primary></indexterm>
> +       Start as soon as a consistent state has been reached in a hot-standby,
> +       allowing processes to connect to databases and run read-only queries.
> +      </para>
> +     </listitem>
> +    </varlistentry>
> +
> +    <varlistentry>
> +     <term><literal>BgWorkerStart_RecoveryFinished</literal></term>
> +     <listitem>
> +      <para>
> +       <indexterm><primary>BgWorkerStart_RecoveryFinished</primary></indexterm>
> +       Start as soon as the system has entered normal read-write state. Note
> +       that the <literal>BgWorkerStart_ConsistentState</literal> and
> +      <literal>BgWorkerStart_RecoveryFinished</literal> are equivalent
> +       in a server that's not a hot standby.
> +      </para>
> +     </listitem>
> +    </varlistentry>
> +
> +    <varlistentry>
> +     <term><literal>BgWorkerStart_ConsistentState_HotStandby</literal></term>
> +     <listitem>
> +      <para>
> +       <indexterm><primary>BgWorkerStart_ConsistentState_HotStandby</primary></indexterm>
> +       Same meaning as <literal>BgWorkerStart_ConsistentState</literal> but
> +       it is more strict in terms of the server i.e. start the worker only
> +       if it is hot-standby.
> +      </para>
> +     </listitem>
> +    </varlistentry>
> +   </variablelist>
>
> Maybe reorder these slightly, because I felt it is better if the
> BgWorkerStart_ConsistentState_HotStandby comes next after
> BgWorkerStart_ConsistentState, which it refers to
>
> For example::
> 1st.BgWorkerStart_PostmasterStart
> 2nd.BgWorkerStart_ConsistentState
> 3rd.BgWorkerStart_ConsistentState_HotStandby
> 4th.BgWorkerStart_RecoveryFinished
>
> ======
> doc/src/sgml/config.sgml
>
> 2.
> <varname>enable_syncslot</varname> = true
>
> Not sure, but I thought the "= true" part should be formatted too.
>
> SUGGESTION
> <literal>enable_syncslot = true</literal>
>
> ======
> doc/src/sgml/logicaldecoding.sgml
>
> 3.
> +    <para>
> +     A logical replication slot on the primary can be synchronized to the hot
> +     standby by enabling the failover option during slot creation and setting
> +     <varname>enable_syncslot</varname> on the standby. For the synchronization
> +     to work, it is mandatory to have a physical replication slot between the
> +     primary and the standby. It's highly recommended that the said physical
> +     replication slot is listed in <varname>standby_slot_names</varname> on
> +     the primary to prevent the subscriber from consuming changes faster than
> +     the hot standby. Additionally, <varname>hot_standby_feedback</varname>
> +     must be enabled on the standby for the slots synchronization to work.
> +    </para>
>
> I felt those parts that describe the mandatory GUCs should be kept together.
>
> SUGGESTION
> For the synchronization to work, it is mandatory to have a physical
> replication slot between the primary and the standby, and
> <varname>hot_standby_feedback</varname> must be enabled on the
> standby.
>
> It's also highly recommended that the said physical replication slot
> is named in <varname>standby_slot_names</varname> list on the primary,
> to prevent the subscriber from consuming changes faster than the hot
> standby.
>
> ~~~
>
> 4. (Chapter 49)
>
> By enabling synchronization of slots, logical replication can be
> resumed after failover depending upon the
> pg_replication_slots.sync_state for the synchronized slots on the
> standby at the time of failover. Only slots that were in ready
> sync_state ('r') on the standby before failover can be used for
> logical replication after failover. However, the slots which were in
> initiated sync_state ('i') and not sync-ready ('r') at the time of
> failover will be dropped and logical replication for such slots can
> not be resumed after failover. This applies to the case where a
> logical subscription is disabled before failover and is enabled after
> failover. If the synchronized slot due to disabled subscription could
> not be made sync-ready ('r') on standby, then the subscription can not
> be resumed after failover even when enabled. If the primary is idle,
> then the synchronized slots on the standby may take a noticeable time
> to reach the ready ('r') sync_state. This can be sped up by calling
> the pg_log_standby_snapshot function on the primary.
>
> ~
>
> Somehow, I still felt all that was too wordy/repetitive. Below is my
> attempt to make it more concise. Thoughts?
>
> SUGGESTION
> The ability to resume logical replication after failover depends upon
> the pg_replication_slots.sync_state value for the synchronized slots
> on the standby at the time of failover. Only slots that have attained
> a "ready" sync_state ('r') on the standby before failover can be used
> for logical replication after failover. Slots that have not yet
> reached 'r' state (they are still 'i') will be dropped, therefore
> logical replication for those slots cannot be resumed. For example, if
> the synchronized slot could not become sync-ready on standby due to a
> disabled subscription, then the subscription cannot be resumed after
> failover even when it is enabled.
>
> If the primary is idle, the synchronized slots on the standby may take
> a noticeable time to reach the ready ('r') sync_state. This can be
> sped up by calling the pg_log_standby_snapshot function on the
> primary.
>
> ======
> doc/src/sgml/system-views.sgml
>
> 5.
> +      <para>
> +      Defines slot synchronization state. This is meaningful on the physical
> +      standby which has configured <varname>enable_syncslot</varname> = true
> +      </para>
>
> As mentioned in the previous review comment ([1]#10) I thought it
> might be good to include a hyperlink cross-reference to the
> 'enable_syncslot' GUC.
>
> ~~~
>
> 6.
> +      <para>
> +      The hot standby can have any of these sync_state for the slots but on a
> +      hot standby, the slots with state 'r' and 'i' can neither be used for
> +      logical decoding nor dropped by the user. The primary server will have
> +      sync_state as 'n' for all the slots. But if the standby is promoted to
> +      become the new primary server, sync_state can be seen 'r' as well. On
> +      this new primary server, slots with sync_state as 'r' and 'n' will
> +      behave the same.
> +      </para></entry>
>
> 6a.
> /these sync_state for the slots/these sync_state values for the slots/
>
> ~
>
> 6b
> Hm. I still felt (same as previous review [1]#12b) that there seems
> too much information here.
>
> IIUC the sync_state is only meaningful on the standby. Sure, it might
> have some values line 'n' or 'r' on the primary also, but those either
> mean nothing ('n') or are leftover states from a previous failover
> from a standby ('r'), which also means nothing. So can't we just say
> it more succinctly like that?
>
> SUGGESTION
> The sync_state has no meaning on the primary server; the primary
> sync_state value is default 'n' for all slots but may (if leftover
> from a promoted standby)  also be 'r'.
>
> ======
> .../libpqwalreceiver/libpqwalreceiver.c
>
> 7.
>  static void
>  libpqrcv_alter_slot(WalReceiverConn *conn, const char *slotname,
> - bool failover)
> + bool failover)
>
> Still seems to be tampering with indentation that should only be in patch 0001.
>
> ======
> src/backend/replication/logical/slotsync.c
>
> 8. wait_for_primary_slot_catchup
>
> The meaning of the boolean return of this function is still not
> described by the function comment.
>
> ~~~
>
> 9.
> + * If passed, *wait_attempts_exceeded will be set to true only if this
> + * function exits after exhausting its wait attempts. It will be false
> + * in all the other cases like failure, remote-slot invalidation, primary
> + * could catch up.
>
> The above already says when a return false happens, so it seems
> overkill to give more information.
>
> SUGGESTION
> If passed, *wait_attempts_exceeded will be set to true only if this
> function exits due to exhausting its wait attempts. It will be false
> in all the other cases.
>
> ~~~
>
> 10.
>
> +static bool
> +wait_for_primary_slot_catchup(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
> +   bool *wait_attempts_exceeded)
> +{
> +#define WAIT_OUTPUT_COLUMN_COUNT 4
> +#define WORKER_PRIMARY_CATCHUP_WAIT_ATTEMPTS 5
> +
>
> 10a
> Maybe the long constant name is too long. How about
> WAIT_PRIMARY_CATCHUP_ATTEMPTS?
>
> ~~~
>
> 10b.
> IMO it is better to Assert the input value of this kind of side-effect
> return parameter, to give a better understanding and to prevent future
> accidents.
>
> SUGGESTION
> Assert(wait_attempts_exceeded == NULL |} *wait_attempts_exceeded == false);
>
> ~~~
>
> 11. synchronize_one_slot
>
> + ReplicationSlot *s;
> + ReplicationSlot *slot;
> + char sync_state = '\0';
>
> 11a.
> I don't think you need both 's' and 'slot' ReplicationSlot -- it looks
> a bit odd. Can't you just reuse the one 'slot' variable?
>
> ~
>
> 11b.
> Also, maybe those assignment like
> + slot = MyReplicationSlot;
>
> can have an explanatory comment like:
> /* For convenience, we assign MyReplicationSlot to a shorter variable name. */
>

I have changed it to slightly simpler one, if that is okay?

> ~~~
>
> 12.
> +static void
> +synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
> + bool *slot_updated)
> +{
> + ReplicationSlot *s;
> + ReplicationSlot *slot;
> + char sync_state = '\0';
>
> In my previous review [1]#33a I thought it was strange to assign the
> sync_state (which is essentially an enum) to some meaningless value,
> so I suggested it should be set to SYNCSLOT_STATE_NONE in the
> declaration. The reply [2] was "No, that will change the flow. It
> should stay uninitialized if the slot is not found."
>
> But I am not convinced there is any flow problem. Also,
> SYNCSLOT_STATE_NONE seems the naturally correct default for something
> with no state. It cannot be found and be SYNCSLOT_STATE_NONE at the
> same time (that is reported as an ERROR "skipping sync of slot") so I
> see no problem.
>
> The CURRENT code is like this:
>
> /* Slot created by the slot sync worker exists, sync it */
> if (sync_state)
> {
>   Assert(sync_state == SYNCSLOT_STATE_READY || sync_state ==
> SYNCSLOT_STATE_INITIATED);
>   ...
> }
> /* Otherwise create the slot first. */
> else
> {
>   ...
> }
>
> AFAICT that could easily be changed to like below, with no change to
> the logic, and it avoids setting strange values.
>
> SUGGESTION.
>
> if (sync_state == SYNCSLOT_STATE_NONE)
> {
>   /* Slot not found. Create it. */
>   ..
> }
> else
> {
>   Assert(sync_state == SYNCSLOT_STATE_READY || sync_state ==
> SYNCSLOT_STATE_INITIATED);
>   ...
> }
>

I have restructured the entire code here and thus initialization of
sync_state is no longer needed. Please review now and let me know.

> ~~~
>
> 13. synchronize_one_slot
>
> +static void
> +synchronize_one_slot(WalReceiverConn *wrconn, RemoteSlot *remote_slot,
> + bool *slot_updated)
>
> This *slot_updated parameter looks dubious. It is used in a loop from
> the caller to mean that ANY slot was updated -- e.g. maybe it is true
> or false on entry to this function.
>
> But, Instead of having some dependency between this function and the
> caller, IMO it makes more sense if we would make this just a boolean
> function in the first place (e.g. was updated? T/F)
>
> Then the caller can also be written more easily like:
>
> some_slot_updated |= synchronize_one_slot(wrconn, remote_slot);
>
> ~~~
>
> 14.
> + /* Search for the named slot */
> + if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
> + {
> + SpinLockAcquire(&s->mutex);
> + sync_state = s->data.sync_state;
> + SpinLockRelease(&s->mutex);
> +
> + /* User created slot with the same name exists, raise ERROR. */
> + if (sync_state == SYNCSLOT_STATE_NONE)
> + {
> + ereport(ERROR,
> + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> + errmsg("skipping sync of slot \"%s\" as it is a user created"
> + " slot", remote_slot->name),
> + errdetail("This slot has failover enabled on the primary and"
> +    " thus is sync candidate but user created slot with"
> +    " the same name already exists on the standby")));
> + }
> + }
>
>
> Extra curly brackets around the ereport are not needed.
>
> ~~~
>
> 15.
> + /*
> + * Sanity check: With hot_standby_feedback enabled and
> + * invalidations handled apropriately as above, this should never
> + * happen.
> + */
> + if (remote_slot->restart_lsn < slot->data.restart_lsn)
> + {
> + elog(ERROR,
> + "not synchronizing local slot \"%s\" LSN(%X/%X)"
> + " to remote slot's LSN(%X/%X) as synchronization "
> + " would move it backwards", remote_slot->name,
> + LSN_FORMAT_ARGS(slot->data.restart_lsn),
> + LSN_FORMAT_ARGS(remote_slot->restart_lsn));
> + }
>
> 15a.
> /apropriately/appropriately/
>
> ~
>
> 15b.
> Extra curly brackets around the elog are not needed.
>
> ~~~
>
> 16. synchronize_slots
>
> +static bool
> +synchronize_slots(WalReceiverConn *wrconn)
> +{
> +#define SLOTSYNC_COLUMN_COUNT 9
> + Oid slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
> + LSNOID, XIDOID, BOOLOID, BOOLOID, TEXTOID, INT2OID};
> +
> + WalRcvExecResult *res;
> + TupleTableSlot *tupslot;
> + StringInfoData s;
> + List    *remote_slot_list = NIL;
> + MemoryContext oldctx = CurrentMemoryContext;
> + ListCell   *lc;
> + bool slot_updated = false;
>
> Suggest renaming 'slot_updated' to 'some_slot_updated' or
> 'update_occurred' etc because the current name makes it look like it
> applies to a single slot, but it doesn't.
>
> ~~~
>
> 17.
> + SpinLockAcquire(&WalRcv->mutex);
> + if (!WalRcv ||
> + (WalRcv->slotname[0] == '\0') ||
> + XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
> + {
> + SpinLockRelease(&WalRcv->mutex);
> + return slot_updated;
> + }
> + SpinLockRelease(&WalRcv->mutex);
>
> IMO "return false;" here is more clear than saying "return slot_updated;"
>
> ~~~
>
> 18.
> + appendStringInfo(&s,
> + "SELECT slot_name, plugin, confirmed_flush_lsn,"
> + " restart_lsn, catalog_xmin, two_phase, failover,"
> + " database, pg_get_slot_invalidation_cause(slot_name)"
> + " FROM pg_catalog.pg_replication_slots"
> + " WHERE failover and sync_state != 'i'");
>
> 18a.
> /and/AND/
>
> ~
>
> 18b.
> In the reply post (see [2]#32) Shveta said "I could not find quote_*
> function for a character just like we have 'quote_literal_cstr' for
> string". If you still want to use constant substitution instead of
> just hardwired 'i' then why do even you need a quote_* function? I
> thought the appendStringInfo uses a printf style format-string
> internally, so I assumed it is possible to substitute the state char
> directly using '%c'.
>

Since we have removed cascading standby support, this condition
(sync_state != 'i') is no longer needed in the query.

> ~~~
>
> 19.
> +
> +
> +
> + /* We are done, free remote_slot_list elements */
> + list_free_deep(remote_slot_list);
> +
> + walrcv_clear_result(res);
> +
> + return slot_updated;
> +}
>
> Excessive blank lines.
>
> ~~~
>
> 20. validate_primary_slot
>
> + appendStringInfo(&cmd,
> + "SELECT count(*) = 1 from pg_replication_slots "
> + "WHERE slot_type='physical' and slot_name=%s",
> + quote_literal_cstr(PrimarySlotName));
>
>
> /and/AND/
>
> ~~~
>
> 21.
> + slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
> + tuple_ok = tuplestore_gettupleslot(res->tuplestore, true, false, slot);
> + Assert(tuple_ok); /* It must return one tuple */
>
> IMO it's better to use all the var names the same across all
> functions? So call this 'tupslot' like the other
> MakeSingleTupleTableSlot result.
>
> ~~~
>
> 22. validate_slotsync_parameters
>
> +/*
> + * Checks if GUCs are set appropriately before starting slot sync worker
> + *
> + * The slot sync worker can not start if 'enable_syncslot' is off and
> + * since 'enable_syncslot' is ON, check that the other GUC settings
> + * (primary_slot_name, hot_standby_feedback, wal_level, primary_conninfo)
> + * are compatible with slot synchronization. If not, raise ERROR.
> + */
> +static void
> +validate_slotsync_parameters(char **dbname)
> +{
>
> 22a.
> The comment is quite verbose. IMO the 2nd para seems just unnecessary
> detail of the 1st para.
>
> SUGGESTION
> Check that all necessary GUCs for slot synchronization are set
> appropriately. If not, raise an ERROR.
>
> ~~~
>
> 22b.
> IMO (and given what was said in the comment about enable_syncslot must
> be on)the first statement of this function should be:
>
> /* Sanity check. */
> Assert(enable_syncslot);
>
> ~~~
>
> 23. slotsync_reread_config
>
> + old_dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
> + Assert(old_dbname);
>
> (This is same comment as old review [1]#61)
>
> Hmm. I still don't see why this extraction of the dbname cannot be
> deferred until later when you know the PrimaryConnInfo has changed,
> otherwise, it might be redundant to do this. Shveta replied [2] that
> "Once PrimaryConnInfo is changed, we can not get old-dbname.", but I'm
> not so sure. Isn't this walrcv_get_dbname_from_conninfo just doing a
> string search -- Why can't you defer this until you know
> conninfoChanged is true, and then to get the old_dbname, you can just
> pass the old_primary_conninfo. E.g. call like
> walrcv_get_dbname_from_conninfo(old_primary_conninfo); Maybe I am
> mistaken.
>

Sorry missed your point earlier that we can use old_primary_conninfo
to extract dbname later.
I have removed this re-validation now as we will restart the worker in
case of a parameter change similar to the case of logical apply
worker. So these changes are no longer needed.

> ~~
>
> 24.
> + /*
> + * Since we have initialized this worker with the old dbname, thus
> + * exit if dbname changed. Let it get restarted and connect to the new
> + * dbname specified.
> + */
> + if (conninfoChanged && strcmp(old_dbname, new_dbname) != 0)
> + ereport(ERROR,
> + errmsg("exiting slot sync worker as dbname in "
> +    "primary_conninfo changed"));
>
> IIUC when the tablesync has to restart, it emits a LOG message before
> it exits; but it's not an ERROR. So, shouldn't this be similar -- IMO
> it is not an "error" for the user to wish to change the dbname. Maybe
> this should be LOG followed by an explicit exit. If you agree, then it
> might be better to encapsulate such logic in some little function:
>
> // pseudo-code
> void slotsync_worker_restart(const char *msg)
> {
> ereport(LOG, msg...
> exit(0);
> }
>

we can not do proc_exit(0), as then postmaster will not restart it on
clean-exit. I agree with your logic, but will have another argument in
this function to accept 'exit code' from the caller.

> ~~~
>
> 25. ReplSlotSyncWorkerMain
>
> + for (;;)
> + {
> + int rc;
> + long naptime = WORKER_DEFAULT_NAPTIME_MS;
> + TimestampTz now;
> + bool slot_updated;
> +
> + ProcessSlotSyncInterrupts(wrconn);
> +
> + slot_updated = synchronize_slots(wrconn);
>
> Here I think the 'slot_updated' should be renamed to the same name as
> in #16 above (e.g. 'some_slot_updated' or 'any_slot_updated' or
> 'update_occurred' etc).
>
> ~~~
>
> 26. SlotSyncWorkerRegister
>
> + if (!enable_syncslot)
> + {
> + ereport(LOG,
> + errmsg("skipping slots synchronization because enable_syncslot is "
> +    "disabled."));
> + return;
> + }
>
> Instead of saying "because..." in the error message maybe keep the
> message more terse and describe the "because" part in the errdetail
>
> SUGGESTION
> errmsg("skipping slot synchronization")
> errdetail("enable_syncslot is disabled.")
>
>
> ======
> src/backend/replication/slot.c
>
> 27.
> + * sync_state: Defines slot synchronization state. For user created slots, it
> + * is SYNCSLOT_STATE_NONE and for the slots being synchronized on the physical
> + * standby, it is either SYNCSLOT_STATE_INITIATED or SYNCSLOT_STATE_READY
>   */
>  void
>  ReplicationSlotCreate(const char *name, bool db_specific,
>     ReplicationSlotPersistency persistency,
> -   bool two_phase, bool failover)
> +   bool two_phase, bool failover, char sync_state)
>
>
> 27a.
> Why is this comment even mentioning SYNCSLOT_STATE_READY? IIUC it
> doesn't make sense to ever call ReplicationSlotCreate directly setting
> the 'r' state (e.g., bypassing 'i' ???)
>
> ~
>
> 27b.
> Indeed, IMO there should be Assert(sync_state == SYNCSLOT_STATE_NONE
> || syncstate == SYNCSLOT_STATE_INITIATED); to guarantee this.
>
> ======
> src/include/replication/slot.h
>
> 28.
> + /*
> + * Is this a slot created by a sync-slot worker?
> + *
> + * Only relevant for logical slots on the physical standby.
> + */
> + char sync_state;
> +
>
> (probably I am repeating a previous thought here)
>
> The comment says the field is only relevant for standby, and that's
> how I've been visualizing it, and why I had previously suggested even
> renaming it to 'standby_sync_state'. However, replies are saying that
> after failover these sync_states also have "some meaning for the
> primary server".
>
> That's the part I have trouble understanding. IIUC the server states
> are just either all 'n' (means nothing) or 'r' because they are just
> leftover from the old standby state. So, does it *truly* have meaning
> for the server? Or should those states somehow be removed/ignored on
> the new primary? Anyway, the point is that if this field does have
> meaning also on the primary (I doubt) then those details should be in
> this comment. Otherwise "Only relevant ... on the standby" is too
> misleading.
>

I have modified it currently, but I will give another thought on your
suggestions here (and in earlier emails) and will let you know.

> ======
> .../t/050_standby_failover_slots_sync.pl
>

We are working on CFbot failure fixes in this file and restructing the
tests here. Thus I am keeping these test comments on hold and will
address in next version.

> 29.
> +# Create table and publication on primary
> +$primary->safe_psql('postgres', "CREATE TABLE tab_mypub3 (a int
> PRIMARY KEY);");
> +$primary->safe_psql('postgres', "CREATE PUBLICATION mypub3 FOR TABLE
> tab_mypub3;");
> +
>
> 29a.
> /on primary/on the primary/
>
> ~
>
> 29b.
> Consider to combine those DDL
>
> ~
>
> 29c.
> Perhaps for consistency, you should be calling this 'regress_mypub3'.
>
> ~~~
>
> 30.
> +# Create a subscriber node
> +my $subscriber3 = PostgreSQL::Test::Cluster->new('subscriber3');
> +$subscriber3->init(allows_streaming => 'logical');
> +$subscriber3->start;
> +$subscriber3->safe_psql('postgres', "CREATE TABLE tab_mypub3 (a int
> PRIMARY KEY);");
> +
> +# Create a subscription with failover = true & wait for sync to complete.
> +$subscriber3->safe_psql('postgres',
> + "CREATE SUBSCRIPTION mysub3 CONNECTION '$publisher_connstr' "
> +   . "PUBLICATION mypub3 WITH (slot_name = lsub3_slot, failover = true);");
> +$subscriber3->wait_for_subscription_sync;
>
> 30a
> Consider combining those DDLs.
>
> ~
>
> 30b.
> Probably for consistency, you should be calling this 'regress_mysub3'.
>
> ~~~
>
> 31.
> +# Advance lsn on the primary
> +$primary->safe_psql('postgres',
> + "SELECT pg_log_standby_snapshot();");
> +$primary->safe_psql('postgres',
> + "SELECT pg_log_standby_snapshot();");
> +$primary->safe_psql('postgres',
> + "SELECT pg_log_standby_snapshot();");
> +
>
> Consider combining all those DDLs.
>
> ~~~
>
> 32.
> +# Truncate table on primary
> +$primary->safe_psql('postgres',
> + "TRUNCATE TABLE tab_mypub3;");
> +
> +# Insert data on the primary
> +$primary->safe_psql('postgres',
> + "INSERT INTO tab_mypub3 SELECT generate_series(1, 10);");
> +
>
> Consider combining those DDLs.
>
> ~~~
>
> 33.
> +# Confirm that restart_lsn of lsub3_slot slot is synced to the standby
> +$result = $standby3->safe_psql('postgres',
> + qq[SELECT '$primary_lsn' >= restart_lsn from pg_replication_slots
> WHERE slot_name = 'lsub3_slot';]);
> +is($result, 't', 'restart_lsn of slot lsub3_slot synced to standby');
>
>
> Does "'$primary_lsn' >= restart_lsn" make sense here? NOTE, the sign
> was '<=' in v43-0002
>
> ~~~
>
> 34.
> +# Confirm that confirmed_flush_lsn of lsub3_slot slot is synced to the standby
> +$result = $standby3->safe_psql('postgres',
> + qq[SELECT '$primary_lsn' >= confirmed_flush_lsn from
> pg_replication_slots WHERE slot_name = 'lsub3_slot';]);
> +is($result, 't', 'confirmed_flush_lsn of slot lsub3_slot synced to
> the standby');
>
> Does "'$primary_lsn' >= confirmed_flush_lsn" make sense here? NOTE,
> the sign was '<=' in v43-0002
>
> ~~~
>
> 35.
> +##################################################
> +# Test that synchronized slot can neither be docoded nor dropped by the user
> +##################################################
>
> 35a.
> /docoded/decoded/
>
> ~
>
> 35b.
> Please give explanation in the comment *why* those ops are not allowed
> (e.g. because the hot_standby_feedback GUC does not have an accepted
> value)
>
> ~~~
>
> 36.
> +##################################################
> +# Create another slot which stays in sync_state as initiated ('i')
> +##################################################
> +
>
> Please explain the comment as to *why* it gets stuck in the initiated state.
>
>
> ~~~
>
> 37.
> +##################################################
> +# Promote the standby3 to primary. Confirm that:
> +# a) the sync-ready('r') slot 'lsub3_slot' is retained on new primary
> +# b) the initiated('i') slot 'logical_slot'is dropped on promotion
> +# c) logical replication for mysub3 is resumed succesfully after failover
> +##################################################
>
>
> /'logical_slot'is/'logical_slot' is/ (missing space)
>
> /succesfully/successfully/
>
> ~~~
>
> 38.
> +# Update subscription with new primary's connection info
> +$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 DISABLE;");
> +$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3
> CONNECTION '$standby3_conninfo';");
> +$subscriber3->safe_psql('postgres', "ALTER SUBSCRIPTION mysub3 ENABLE;");
>
>
> Consider combining all those DDLs.
>
> ~~~
>
> 39.
> +
> +# Insert data on the new primary
> +$standby3->safe_psql('postgres',
> + "INSERT INTO tab_mypub3 SELECT generate_series(11, 20);");
> +
> +# Confirm that data in tab_mypub3 replicated on subscriber
> +is( $subscriber3->safe_psql('postgres', q{SELECT count(*) FROM tab_mypub3;}),
> + "20",
> + 'data replicated from new primary');
>
> Shouldn't there be some wait_for_subscription_sync logic (or similar)
> here just to ensure the subscriber3 had time to receive that data
> before you immediately check that it had arrived?
>
> ======
> [1] My v43-0002 review.
> https://www.postgresql.org/message-id/CAHut%2BPuuqEpDse5msENsVuK3rjTRN-QGS67rRCGVv%2BzcT-f0GA%40mail...
> [2] Replies to v43-0002 review.
> https://www.postgresql.org/message-id/CAJpy0uDcOf5Hvk_CdCCAbfx9SY%2Bog%3D%3D%3DtgiuhWKzkYyqebui9g%40...
>
> Kind Regards,
> Peter Smith.
> Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 08:17                                             ` Re: Synchronizing slots from primary to standby Dilip Kumar <[email protected]>
  2023-12-11 08:50                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-11 11:17                                                 ` Dilip Kumar <[email protected]>
  1 sibling, 0 replies; 113+ messages in thread

From: Dilip Kumar @ 2023-12-11 11:17 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Mon, Dec 11, 2023 at 2:21 PM shveta malik <[email protected]> wrote:
>
> On Mon, Dec 11, 2023 at 1:47 PM Dilip Kumar <[email protected]> wrote:
> >
> > On Fri, Dec 8, 2023 at 2:36 PM Amit Kapila <[email protected]> wrote:
> > >
> > > On Wed, Dec 6, 2023 at 4:53 PM shveta malik <[email protected]> wrote:
> > > >
> > > > PFA v43, changes are:
> > > >
> > >
> > > I wanted to discuss 0003 patch about cascading standby's. It is not
> > > clear to me whether we want to allow physical standbys to further wait
> > > for cascading standby to sync their slots. If we allow such a feature
> > > one may expect even primary to wait for all the cascading standby's
> > > because otherwise still logical subscriber can be ahead of one of the
> > > cascading standby. I feel even if we want to allow such a behaviour we
> > > can do it later once the main feature is committed. I think it would
> > > be good to just allow logical walsenders on primary to wait for
> > > physical standbys represented by GUC 'standby_slot_names'. If we agree
> > > on that then it would be good to prohibit setting this GUC on standby
> > > or at least it should be a no-op even if this GUC should be set on
> > > physical standby.
> > >
> > > Thoughts?
> >
> > IMHO, why not keep the behavior consistent across primary and standby?
> >  I mean if it doesn't require a lot of new code/design addition then
> > it should be the user's responsibility.  I mean if the user has set
> > 'standby_slot_names' on standby then let standby also wait for
> > cascading standby to sync their slots?  Is there any issue with that
> > behavior?
> >
>
> Without waiting for cascading standby on primary, it won't be helpful
> to just wait on standby.
>
> Currently logical walsenders on primary waits for physical standbys to
> take changes before they update their own logical slots. But they wait
> only for their immediate standbys and not for cascading standbys.
> Although, on first standby, we do have logic where slot-sync workers
> wait for cascading standbys before they update their own slots (synced
> ones, see patch3). But this does not guarantee that logical
> subscribers on primary will never be ahead of the cascading standbys.
> Let us consider this timeline:
>
> t1: logical walsender on primary waiting for standby1 (first standby).
> t2: physical walsender on standby1 is stuck and thus there is delay in
> sending these changes to standby2 (cascading standby).
> t3: standby1 has taken changes and sends confirmation to primary.
> t4: logical walsender on primary receives confirmation from standby1
> and updates slot, logical subscribers of primary also receives the
> changes.
> t5: standby2 has not received changes yet as physical walsender on
> standby1 is still stuck, slotsync worker still waiting for standby2
> (cascading) before it updates its own slots (synced ones).
> t6: standby2 is promoted to become primary.
>
> Now we are in a state wherein primary, logical subscriber and first
> standby has some changes but cascading standby does not. And logical
> slots on primary were updated w/o confirming if cascading standby has
> taken changes or not. This is a problem and we do not have a simple
> solution for this yet.

Okay, I think that makes sense.


-- 
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-10 11:03                                           ` Amit Kapila <[email protected]>
  2023-12-11 09:11                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  5 siblings, 1 reply; 113+ messages in thread

From: Amit Kapila @ 2023-12-10 11:03 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Wed, Dec 6, 2023 at 4:53 PM shveta malik <[email protected]> wrote:
>
> v43-002:
>

Review comments on v43-0002:
=========================
1.
synchronize_one_slot()
{
...
+ /*
+ * With hot_standby_feedback enabled and invalidations handled
+ * apropriately as above, this should never happen.
+ */
+ if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn)
+ {
+ ereport(ERROR,
+ errmsg("not synchronizing local slot \"%s\" LSN(%X/%X)"
+    " to remote slot's LSN(%X/%X) as synchronization "
+    " would move it backwards", remote_slot->name,
+    LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
+    LSN_FORMAT_ARGS(remote_slot->restart_lsn)));
+
+ goto cleanup;
...
}

After the error, the control won't return, so the above goto doesn't
make any sense.

2.
synchronize_one_slot()
{
...
+ /* Search for the named slot */
+ if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
+ {
+ SpinLockAcquire(&s->mutex);
+ sync_state = s->data.sync_state;
+ SpinLockRelease(&s->mutex);
+ }
...
...
+ ReplicationSlotAcquire(remote_slot->name, true);
+
+ /*
+ * Copy the invalidation cause from remote only if local slot is not
+ * invalidated locally, we don't want to overwrite existing one.
+ */
+ if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
+ {
+ SpinLockAcquire(&MyReplicationSlot->mutex);
+ MyReplicationSlot->data.invalidated = remote_slot->invalidated;
+ SpinLockRelease(&MyReplicationSlot->mutex);
+ }
+
+ /* Skip the sync if slot has been invalidated locally. */
+ if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
+ goto cleanup;
...

It seems useless to acquire the slot if it is locally invalidated in
the first place. Won't it be better if after the search we first check
whether the slot is locally invalidated and take appropriate action?

3. After doing the above two, I think it doesn't make sense to have
goto at the remaining places in synchronize_one_slot(). We can simply
release the slot and commit the transaction at other places.

4.
+ * Returns nap time for the next sync-cycle.
+ */
+static long
+synchronize_slots(WalReceiverConn *wrconn)

Returning nap time from here appears a bit awkward. I think it is
better if this function returns any_slot_updated and then the caller
decides the adjustment of naptime.

5.
+synchronize_slots(WalReceiverConn *wrconn)
{
...
...
+ /* The syscache access needs a transaction env. */
+ StartTransactionCommand();
+
+ /*
+ * Make result tuples live outside TopTransactionContext to make them
+ * accessible even after transaction is committed.
+ */
+ MemoryContextSwitchTo(oldctx);
+
+ /* Construct query to get slots info from the primary server */
+ initStringInfo(&s);
+ construct_slot_query(&s);
+
+ elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
+
+ /* Execute the query */
+ res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
+ pfree(s.data);
+
+ if (res->status != WALRCV_OK_TUPLES)
+ ereport(ERROR,
+ (errmsg("could not fetch failover logical slots info "
+ "from the primary server: %s", res->err)));
+
+ CommitTransactionCommand();
...
...
}

Where exactly in the above code, there is a syscache access as
mentioned above StartTransactionCommand()?

6.
-          <filename>~/.pgpass</filename> file on the standby server (use
+          <filename>~/.pgpass</filename> file on the standby server. (use
           <literal>replication</literal> as the database name).

Why do we need this change?

7.
+     standby. Additionally, similar to creating a logical replication slot
+     on the hot standby, <varname>hot_standby_feedback</varname> should be
+     set on the standby and a physical slot between the primary and the standby
+     should be used.

In this, I don't understand the relation between the first part of the
line: "Additionally, similar to creating a logical replication slot on
the hot standby ..." with the rest.

8.
However,
+     the slots which were in initiated sync_state ('i) and were not

A single quote after 'i' is missing.

9.
the slots with state 'r' and 'i' can neither be used for logical
+      decoded nor dropped by the user.

/decoded/decoding

10.
+/*
+ * Allocate and initialize slow sync worker shared memory
+ */

/slow/slot

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-10 11:03                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-12-11 09:11                                             ` shveta malik <[email protected]>
  2023-12-11 13:42                                               ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-11 09:11 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Sun, Dec 10, 2023 at 4:33 PM Amit Kapila <[email protected]> wrote:
>
> On Wed, Dec 6, 2023 at 4:53 PM shveta malik <[email protected]> wrote:
> >
> > v43-002:
> >
>
> Review comments on v43-0002:
> =========================

Thanks for the feedback Amit. Addressed these in v45. Please find my
response on a few of these.

> 1.
> synchronize_one_slot()
> {
> ...
> + /*
> + * With hot_standby_feedback enabled and invalidations handled
> + * apropriately as above, this should never happen.
> + */
> + if (remote_slot->restart_lsn < MyReplicationSlot->data.restart_lsn)
> + {
> + ereport(ERROR,
> + errmsg("not synchronizing local slot \"%s\" LSN(%X/%X)"
> +    " to remote slot's LSN(%X/%X) as synchronization "
> +    " would move it backwards", remote_slot->name,
> +    LSN_FORMAT_ARGS(MyReplicationSlot->data.restart_lsn),
> +    LSN_FORMAT_ARGS(remote_slot->restart_lsn)));
> +
> + goto cleanup;
> ...
> }
>
> After the error, the control won't return, so the above goto doesn't
> make any sense.
>
> 2.
> synchronize_one_slot()
> {
> ...
> + /* Search for the named slot */
> + if ((s = SearchNamedReplicationSlot(remote_slot->name, true)))
> + {
> + SpinLockAcquire(&s->mutex);
> + sync_state = s->data.sync_state;
> + SpinLockRelease(&s->mutex);
> + }
> ...
> ...
> + ReplicationSlotAcquire(remote_slot->name, true);
> +
> + /*
> + * Copy the invalidation cause from remote only if local slot is not
> + * invalidated locally, we don't want to overwrite existing one.
> + */
> + if (MyReplicationSlot->data.invalidated == RS_INVAL_NONE)
> + {
> + SpinLockAcquire(&MyReplicationSlot->mutex);
> + MyReplicationSlot->data.invalidated = remote_slot->invalidated;
> + SpinLockRelease(&MyReplicationSlot->mutex);
> + }
> +
> + /* Skip the sync if slot has been invalidated locally. */
> + if (MyReplicationSlot->data.invalidated != RS_INVAL_NONE)
> + goto cleanup;
> ...
>
> It seems useless to acquire the slot if it is locally invalidated in
> the first place. Won't it be better if after the search we first check
> whether the slot is locally invalidated and take appropriate action?
>

 If we don't acquire the slot first, there could be a race condition
that the local slot could be invalidated just after checking the
invalidated flag. See InvalidatePossiblyObsoleteSlot() where it
invalidates slot directly if the slot is not acquired by other
processes. Thus, I have not removed 'ReplicationSlotAcquire' but I
have re-structured the code a little bit to get rid of duplicate code
in 'if' and 'else' part for invalidation logic.

> 3. After doing the above two, I think it doesn't make sense to have
> goto at the remaining places in synchronize_one_slot(). We can simply
> release the slot and commit the transaction at other places.
>
> 4.
> + * Returns nap time for the next sync-cycle.
> + */
> +static long
> +synchronize_slots(WalReceiverConn *wrconn)
>
> Returning nap time from here appears a bit awkward. I think it is
> better if this function returns any_slot_updated and then the caller
> decides the adjustment of naptime.
>
> 5.
> +synchronize_slots(WalReceiverConn *wrconn)
> {
> ...
> ...
> + /* The syscache access needs a transaction env. */
> + StartTransactionCommand();
> +
> + /*
> + * Make result tuples live outside TopTransactionContext to make them
> + * accessible even after transaction is committed.
> + */
> + MemoryContextSwitchTo(oldctx);
> +
> + /* Construct query to get slots info from the primary server */
> + initStringInfo(&s);
> + construct_slot_query(&s);
> +
> + elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
> +
> + /* Execute the query */
> + res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
> + pfree(s.data);
> +
> + if (res->status != WALRCV_OK_TUPLES)
> + ereport(ERROR,
> + (errmsg("could not fetch failover logical slots info "
> + "from the primary server: %s", res->err)));
> +
> + CommitTransactionCommand();
> ...
> ...
> }
>
> Where exactly in the above code, there is a syscache access as
> mentioned above StartTransactionCommand()?
>

It is in walrcv_exec (libpqrcv_processTuples). I have changed the
comments to add this info.

> 6.
> -          <filename>~/.pgpass</filename> file on the standby server (use
> +          <filename>~/.pgpass</filename> file on the standby server. (use
>            <literal>replication</literal> as the database name).
>
> Why do we need this change?

We don't, removed it.

>
> 7.
> +     standby. Additionally, similar to creating a logical replication slot
> +     on the hot standby, <varname>hot_standby_feedback</varname> should be
> +     set on the standby and a physical slot between the primary and the standby
> +     should be used.
>
> In this, I don't understand the relation between the first part of the
> line: "Additionally, similar to creating a logical replication slot on
> the hot standby ..." with the rest.
>
> 8.
> However,
> +     the slots which were in initiated sync_state ('i) and were not
>
> A single quote after 'i' is missing.
>
> 9.
> the slots with state 'r' and 'i' can neither be used for logical
> +      decoded nor dropped by the user.
>
> /decoded/decoding
>
> 10.
> +/*
> + * Allocate and initialize slow sync worker shared memory
> + */
>
> /slow/slot
>
> --
> With Regards,
> Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-10 11:03                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 09:11                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-11 13:42                                               ` Amit Kapila <[email protected]>
  2023-12-12 09:14                                                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Amit Kapila @ 2023-12-11 13:42 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Mon, Dec 11, 2023 at 2:41 PM shveta malik <[email protected]> wrote:
>
> >
> > 5.
> > +synchronize_slots(WalReceiverConn *wrconn)
> > {
> > ...
> > ...
> > + /* The syscache access needs a transaction env. */
> > + StartTransactionCommand();
> > +
> > + /*
> > + * Make result tuples live outside TopTransactionContext to make them
> > + * accessible even after transaction is committed.
> > + */
> > + MemoryContextSwitchTo(oldctx);
> > +
> > + /* Construct query to get slots info from the primary server */
> > + initStringInfo(&s);
> > + construct_slot_query(&s);
> > +
> > + elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
> > +
> > + /* Execute the query */
> > + res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
> > + pfree(s.data);
> > +
> > + if (res->status != WALRCV_OK_TUPLES)
> > + ereport(ERROR,
> > + (errmsg("could not fetch failover logical slots info "
> > + "from the primary server: %s", res->err)));
> > +
> > + CommitTransactionCommand();
> > ...
> > ...
> > }
> >
> > Where exactly in the above code, there is a syscache access as
> > mentioned above StartTransactionCommand()?
> >
>
> It is in walrcv_exec (libpqrcv_processTuples). I have changed the
> comments to add this info.
>

Okay, I see that the patch switches context twice once after starting
the transaction and the second time after committing the transaction,
why is that required? Also, can't we extend the duration of the
transaction till the remote_slot information is constructed? I am
asking this because the context used is TopMemoryContext which should
be used only if we need something specific to be retained at the
process level which doesn't seem to be the case here.

I have noticed a few other minor things:
1.
postgres=# select * from pg_logical_slot_get_changes('log_slot_2', NULL, NULL);
ERROR:  cannot use replication slot "log_slot_2" for logical decoding
DETAIL:  This slot is being synced from the primary server.
...
...
postgres=# select * from pg_drop_replication_slot('log_slot_2');
ERROR:  cannot drop replication slot "log_slot_2"
DETAIL:  This slot is being synced from the primary.

I think the DETAIL message should be the same in the above two cases.

2.
+void
+WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
+{
+ List    *standby_slots;
+
+ Assert(!am_walsender);
+
+ if (!MyReplicationSlot->data.failover)
+ return;
+
+ standby_slots = GetStandbySlotList(true);
+
+ ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
...
...

Shouldn't we return if the standby slot names list is NIL unless there
is a reason to do ConditionVariablePrepareToSleep() or any of the code
following it?

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-10 11:03                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 09:11                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-11 13:42                                               ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
@ 2023-12-12 09:14                                                 ` shveta malik <[email protected]>
  2023-12-13 03:31                                                   ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-12 09:14 UTC (permalink / raw)
  To: Amit Kapila <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Mon, Dec 11, 2023 at 7:12 PM Amit Kapila <[email protected]> wrote:
>
> On Mon, Dec 11, 2023 at 2:41 PM shveta malik <[email protected]> wrote:
> >
> > >
> > > 5.
> > > +synchronize_slots(WalReceiverConn *wrconn)
> > > {
> > > ...
> > > ...
> > > + /* The syscache access needs a transaction env. */
> > > + StartTransactionCommand();
> > > +
> > > + /*
> > > + * Make result tuples live outside TopTransactionContext to make them
> > > + * accessible even after transaction is committed.
> > > + */
> > > + MemoryContextSwitchTo(oldctx);
> > > +
> > > + /* Construct query to get slots info from the primary server */
> > > + initStringInfo(&s);
> > > + construct_slot_query(&s);
> > > +
> > > + elog(DEBUG2, "slot sync worker's query:%s \n", s.data);
> > > +
> > > + /* Execute the query */
> > > + res = walrcv_exec(wrconn, s.data, SLOTSYNC_COLUMN_COUNT, slotRow);
> > > + pfree(s.data);
> > > +
> > > + if (res->status != WALRCV_OK_TUPLES)
> > > + ereport(ERROR,
> > > + (errmsg("could not fetch failover logical slots info "
> > > + "from the primary server: %s", res->err)));
> > > +
> > > + CommitTransactionCommand();
> > > ...
> > > ...
> > > }
> > >
> > > Where exactly in the above code, there is a syscache access as
> > > mentioned above StartTransactionCommand()?
> > >
> >
> > It is in walrcv_exec (libpqrcv_processTuples). I have changed the
> > comments to add this info.
> >
>
> Okay, I see that the patch switches context twice once after starting
> the transaction and the second time after committing the transaction,
> why is that required? Also, can't we extend the duration of the
> transaction till the remote_slot information is constructed?

If we extend duration, we have to extend till remote_slot information
is consumed and not only till it is constructed.

> I am
> asking this because the context used is TopMemoryContext which should
> be used only if we need something specific to be retained at the
> process level which doesn't seem to be the case here.
>

Okay, I understand your concern. But this needs more thoughts on shall
we have all the slots synchronized in one txn or is it better to have
it existing way i.e. each slot being synchronized in its own txn
started in synchronize_one_slot. If we go by the former, can it have
any implications? I need to review this bit more before concluding.
.
> I have noticed a few other minor things:
> 1.
> postgres=# select * from pg_logical_slot_get_changes('log_slot_2', NULL, NULL);
> ERROR:  cannot use replication slot "log_slot_2" for logical decoding
> DETAIL:  This slot is being synced from the primary server.
> ...
> ...
> postgres=# select * from pg_drop_replication_slot('log_slot_2');
> ERROR:  cannot drop replication slot "log_slot_2"
> DETAIL:  This slot is being synced from the primary.
>
> I think the DETAIL message should be the same in the above two cases.
>
> 2.
> +void
> +WalSndWaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
> +{
> + List    *standby_slots;
> +
> + Assert(!am_walsender);
> +
> + if (!MyReplicationSlot->data.failover)
> + return;
> +
> + standby_slots = GetStandbySlotList(true);
> +
> + ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv);
> ...
> ...
>
> Shouldn't we return if the standby slot names list is NIL unless there
> is a reason to do ConditionVariablePrepareToSleep() or any of the code
> following it?
>
> --
> With Regards,
> Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-10 11:03                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-11 09:11                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-11 13:42                                               ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-12 09:14                                                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-13 03:31                                                   ` Amit Kapila <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Amit Kapila @ 2023-12-13 03:31 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Drouvot, Bertrand <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Tue, Dec 12, 2023 at 2:44 PM shveta malik <[email protected]> wrote:
>
> On Mon, Dec 11, 2023 at 7:12 PM Amit Kapila <[email protected]> wrote:
> >
>
> > I am
> > asking this because the context used is TopMemoryContext which should
> > be used only if we need something specific to be retained at the
> > process level which doesn't seem to be the case here.
> >
>
> Okay, I understand your concern. But this needs more thoughts on shall
> we have all the slots synchronized in one txn or is it better to have
> it existing way i.e. each slot being synchronized in its own txn
> started in synchronize_one_slot. If we go by the former, can it have
> any implications?
>

I think the one advantage of syncing each slot in a different
transaction could have been if that helps with the visibility of
updated slot information but that is not the case here as we always
persist it to file. As per my understanding, here we need a
transaction as we may access catalogs while creating/updating slots,
so, a single transaction should be okay unless there are any other
reasons.

-- 
With Regards,
Amit Kapila.






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-07 07:49                                         ` Drouvot, Bertrand <[email protected]>
  2023-12-07 09:07                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-12-07 07:49 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 12/6/23 11:58 AM, shveta malik wrote:
> On Wed, Dec 6, 2023 at 3:00 PM Drouvot, Bertrand
> <[email protected]> wrote:
>>
>> Hi,
>>
>> On 12/6/23 7:18 AM, shveta malik wrote:
>>> On Wed, Dec 6, 2023 at 10:56 AM Amit Kapila <[email protected]> wrote:
>>>>
>>>> I feel that is indirectly relying on the fact that the primary won't
>>>> advance logical slots unless physical standby has consumed data.
>>>
>>> Yes, that is the basis of this discussion.
>>
>> Yes.
>>
>>> But now on rethinking, if
>>> the user has not set 'standby_slot_names' on primary at first pace,
>>> then even if walreceiver on standby is down, slots on primary will
>>> keep on advancing
>>
>> Oh right, good point.
>>
>>> and thus we need to sync.
>>
>> Yes and I think our current check "XLogRecPtrIsInvalid(WalRcv->latestWalEnd)"
>> in synchronize_slots() prevents us to do so (as I think WalRcv->latestWalEnd
>> would be invalid for a non started walreceiver).
>>
> 
> But I think we do not need to deal with the case that walreceiver is
> not started at all on standby. It is always started. Walreceiver not
> getting started or down for long is a rare scenario. We have other
> checks too for 'latestWalEnd' in slotsync worker and I think we should
> retain those as is.
> 

Agree to not deal with the walreceiver being down for now (we can
still improve that part later if we encounter the case in the real
world).

Might be worth to add comments in the code (around the WalRcv->latestWalEnd
checks) that no "lagging" sync are possible if the walreceiver is not started
though?
  

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 07:49                                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-12-07 09:07                                           ` shveta malik <[email protected]>
  2023-12-07 09:27                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: shveta malik @ 2023-12-07 09:07 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Amit Kapila <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Thu, Dec 7, 2023 at 1:19 PM Drouvot, Bertrand
<[email protected]> wrote:
>
> Hi,
>
> On 12/6/23 11:58 AM, shveta malik wrote:
> > On Wed, Dec 6, 2023 at 3:00 PM Drouvot, Bertrand
> > <[email protected]> wrote:
> >>
> >> Hi,
> >>
> >> On 12/6/23 7:18 AM, shveta malik wrote:
> >>> On Wed, Dec 6, 2023 at 10:56 AM Amit Kapila <[email protected]> wrote:
> >>>>
> >>>> I feel that is indirectly relying on the fact that the primary won't
> >>>> advance logical slots unless physical standby has consumed data.
> >>>
> >>> Yes, that is the basis of this discussion.
> >>
> >> Yes.
> >>
> >>> But now on rethinking, if
> >>> the user has not set 'standby_slot_names' on primary at first pace,
> >>> then even if walreceiver on standby is down, slots on primary will
> >>> keep on advancing
> >>
> >> Oh right, good point.
> >>
> >>> and thus we need to sync.
> >>
> >> Yes and I think our current check "XLogRecPtrIsInvalid(WalRcv->latestWalEnd)"
> >> in synchronize_slots() prevents us to do so (as I think WalRcv->latestWalEnd
> >> would be invalid for a non started walreceiver).
> >>
> >
> > But I think we do not need to deal with the case that walreceiver is
> > not started at all on standby. It is always started. Walreceiver not
> > getting started or down for long is a rare scenario. We have other
> > checks too for 'latestWalEnd' in slotsync worker and I think we should
> > retain those as is.
> >
>
> Agree to not deal with the walreceiver being down for now (we can
> still improve that part later if we encounter the case in the real
> world).
>

yes, agreed.

> Might be worth to add comments in the code (around the WalRcv->latestWalEnd
> checks) that no "lagging" sync are possible if the walreceiver is not started
> though?
>

I am a bit confused. Do you mean as a TODO item? Otherwise the comment
will be opposite of the code we are writing.

>
> Regards,
>
> --
> Bertrand Drouvot
> PostgreSQL Contributors Team
> RDS Open Source Databases
> Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 07:49                                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-07 09:07                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
@ 2023-12-07 09:27                                             ` Drouvot, Bertrand <[email protected]>
  2023-12-08 03:03                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  0 siblings, 1 reply; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-12-07 09:27 UTC (permalink / raw)
  To: shveta malik <[email protected]>; +Cc: Amit Kapila <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 12/7/23 10:07 AM, shveta malik wrote:
> On Thu, Dec 7, 2023 at 1:19 PM Drouvot, Bertrand
> <[email protected]> wrote:
>> Might be worth to add comments in the code (around the WalRcv->latestWalEnd
>> checks) that no "lagging" sync are possible if the walreceiver is not started
>> though?
>>
> 
> I am a bit confused. Do you mean as a TODO item? Otherwise the comment
> will be opposite of the code we are writing.

Sorry for the confusion: what I meant to say is that
synchronization (should it be lagging) is not possible if the walreceiver is not started
(as XLogRecPtrIsInvalid(WalRcv->latestWalEnd) would be true).

More precisely here (in synchronize_slots()):

     /* The primary_slot_name is not set yet or WALs not received yet */
     SpinLockAcquire(&WalRcv->mutex);
     if (!WalRcv ||
         (WalRcv->slotname[0] == '\0') ||
         XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
     {
         SpinLockRelease(&WalRcv->mutex);
         return naptime;
     }

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
  2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
  2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 07:49                                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-07 09:07                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-12-07 09:27                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-12-08 03:03                                               ` shveta malik <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: shveta malik @ 2023-12-08 03:03 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; +Cc: Amit Kapila <[email protected]>; Nisha Moond <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Peter Smith <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>; shveta malik <[email protected]>

On Thu, Dec 7, 2023 at 2:57 PM Drouvot, Bertrand
<[email protected]> wrote:
>
> Hi,
>
> On 12/7/23 10:07 AM, shveta malik wrote:
> > On Thu, Dec 7, 2023 at 1:19 PM Drouvot, Bertrand
> > <[email protected]> wrote:
> >> Might be worth to add comments in the code (around the WalRcv->latestWalEnd
> >> checks) that no "lagging" sync are possible if the walreceiver is not started
> >> though?
> >>
> >
> > I am a bit confused. Do you mean as a TODO item? Otherwise the comment
> > will be opposite of the code we are writing.
>
> Sorry for the confusion: what I meant to say is that
> synchronization (should it be lagging) is not possible if the walreceiver is not started
> (as XLogRecPtrIsInvalid(WalRcv->latestWalEnd) would be true).
>

Sure, I will add it. Thanks for the clarification.

> More precisely here (in synchronize_slots()):
>
>      /* The primary_slot_name is not set yet or WALs not received yet */
>      SpinLockAcquire(&WalRcv->mutex);
>      if (!WalRcv ||
>          (WalRcv->slotname[0] == '\0') ||
>          XLogRecPtrIsInvalid(WalRcv->latestWalEnd))
>      {
>          SpinLockRelease(&WalRcv->mutex);
>          return naptime;
>      }
>
> Regards,
>
> --
> Bertrand Drouvot
> PostgreSQL Contributors Team
> RDS Open Source Databases
> Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
@ 2023-12-04 03:33                 ` Zhijie Hou (Fujitsu) <[email protected]>
  2023-12-04 14:37                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  1 sibling, 1 reply; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-12-04 03:33 UTC (permalink / raw)
  To: Drouvot, Bertrand <[email protected]>; shveta malik <[email protected]>; +Cc: Peter Smith <[email protected]>; Amit Kapila <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Wednesday, November 29, 2023 5:55 PM Drouvot, Bertrand <[email protected]> wrote:

Hi,

> On 11/29/23 6:58 AM, Zhijie Hou (Fujitsu) wrote:
> > On Tuesday, November 28, 2023 8:07 PM Drouvot, Bertrand
> <[email protected]> wrote:
> >
> > Hi,
> >
> >> On 11/27/23 9:57 AM, Zhijie Hou (Fujitsu) wrote:
> >>> On Monday, November 27, 2023 4:51 PM shveta malik
> >> <[email protected]> wrote:
> >>>
> >>> Here is the updated version(v39_2) which include all the changes
> >>> made in
> >> 0002.
> >>> Please use for review, and sorry for the confusion.
> >>>
> >>
> >> Thanks!
> >>
> >> As far v39_2-0001:
> >>
> >> "
> >>       Altering the failover option of the subscription is currently not
> >>       permitted. However, this restriction may be lifted in future versions.
> >> "
> >>
> >> Should we mention that we can alter the related replication slot?
> >
> > Will add.
> >
> >>
> >> +         <para>
> >> +          The implementation of failover requires that replication
> >> +          has successfully finished the initial table synchronization
> >> +          phase. So even when <literal>failover</literal> is enabled for a
> >> +          subscription, the internal failover state remains
> >> +          temporarily <quote>pending</quote> until the
> >> + initialization
> >> phase
> >> +          completes. See column
> >> <structfield>subfailoverstate</structfield>
> >> +          of <link
> >>
> linkend="catalog-pg-subscription"><structname>pg_subscription</struct
> >> na
> >> me></link>
> >> +          to know the actual failover state.
> >> +         </para>
> >>
> >> I think we have a corner case here. If one alter the replication slot
> >> on the primary then "subfailoverstate" is not updated accordingly on the
> subscriber.
> >> Given the 2 remarks above would that make sense to prevent altering a
> >> replication slot associated to a subscription?
> >
> > Thanks for the review!
> >
> > I think we could not distinguish the user created logical slot or
> > subscriber created slot as there is no related info in slot's data.
> 
> Yeah that would need extra work.
> 
> > And user could change
> > the slot on subscription by "alter sub set (slot_name)", so
> > maintaining this info would need some efforts.
> >
> 
> Yes.
> 
> > Besides, I think this case overlaps the previous discussed "alter sub
> > set (slot_name)" issue[1]. Both the cases are because the slot's
> > failover is different from the subscription's failover setting.
> 
> Yeah agree.
> 
> > I think we could handle
> > them similarly that user need to take care of not changing the
> > failover to wrong value. Or do you prefer another approach that
> > mentioned in that thread[1] ? (always alter the slot at the startup of apply
> worker).
> >
> 
> I think I'm fine with documenting the fact that the user should not change the
> failover value. But if he does change it (because at the end nothing prevents it
> to do so) then I think the meaning of subfailoverstate should still make sense.
> 
> One way to achieve this could be to change its meaning? Say rename it to say
> subfailovercreationstate (to reflect the fact that it was the state at the creation
> time) and change messages like:
> 
> "
> ALTER SUBSCRIPTION with refresh and copy_data is not allowed when failover
> is enabled "
> 
> to something like
> 
> "
> ALTER SUBSCRIPTION with refresh and copy_data is not allowed for
> subscription created with failover enabled"
> "
> 
> and change the doc accordingly.
> 
> What do you think?

This idea may work for now, but I think we planned to support ALTER
SUBSCRIPTION (failover) in a later patch, which means the meaning of
subfailovercreationstate may be invalid after that because we will be able to
change this value using ALTER SUBSCRIPTION as well.

I think document the case is OK because:

Currently, user already can create similar inconsistency cases as we don't restrict
user to change the slot on publisher. E.g., User could drop and recreate the
slot used by subscription but with different setting. Or user ALTER
SUBSCRIPTION set (slot_name) to switch to a new slot with different setting.

For example, about two_phase option, user can create a subscription with
two_phase disabled, then later it can set subscription slot_name to a new slot
with two_phase enabled which is the similar case as the failover.

Best Regards,
Hou zj


^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-12-04 03:33                 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-12-04 14:37                   ` Drouvot, Bertrand <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Drouvot, Bertrand @ 2023-12-04 14:37 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; shveta malik <[email protected]>; +Cc: Peter Smith <[email protected]>; Amit Kapila <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi,

On 12/4/23 4:33 AM, Zhijie Hou (Fujitsu) wrote:
> On Wednesday, November 29, 2023 5:55 PM Drouvot, Bertrand <[email protected]> wrote:
> 
>> I think I'm fine with documenting the fact that the user should not change the
>> failover value. But if he does change it (because at the end nothing prevents it
>> to do so) then I think the meaning of subfailoverstate should still make sense.
>>
>> One way to achieve this could be to change its meaning? Say rename it to say
>> subfailovercreationstate (to reflect the fact that it was the state at the creation
>> time) and change messages like:
>>
>> "
>> ALTER SUBSCRIPTION with refresh and copy_data is not allowed when failover
>> is enabled "
>>
>> to something like
>>
>> "
>> ALTER SUBSCRIPTION with refresh and copy_data is not allowed for
>> subscription created with failover enabled"
>> "
>>
>> and change the doc accordingly.
>>
>> What do you think?

> I think document the case is OK because:
> 
> Currently, user already can create similar inconsistency cases as we don't restrict
> user to change the slot on publisher. E.g., User could drop and recreate the
> slot used by subscription but with different setting. Or user ALTER
> SUBSCRIPTION set (slot_name) to switch to a new slot with different setting.
> 
> For example, about two_phase option, user can create a subscription with
> two_phase disabled, then later it can set subscription slot_name to a new slot
> with two_phase enabled which is the similar case as the failover.
> 

Yeah, right, did not think that such "inconsistency" can already happen.

So agree to keep "subfailoverstate" and "just" document the case then.

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* Re: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
@ 2023-11-29 03:04           ` Peter Smith <[email protected]>
  2023-11-29 09:12             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2 siblings, 1 reply; 113+ messages in thread

From: Peter Smith @ 2023-11-29 03:04 UTC (permalink / raw)
  To: Zhijie Hou (Fujitsu) <[email protected]>; +Cc: shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; Amit Kapila <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

Hi. Here are some review comments for the patch v39_2-0001.

Multiple items from my previous review [1] seemed unanswered, so it
wasn't clear if they were discarded because they were wrong or maybe
accidently missed. I've repeated all those again here, as well as some
new comments.

======
1. General.

Previously (see [1] #0) I asked a question about if there is some
documentation missing. Seems not yet answered.

======
Commit message

2.
Users can set this flag during CREATE SUBSCRIPTION or during
pg_create_logical_replication_slot API. Examples:

CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub
WITH (failover = true);

(failover is the last arg)
SELECT * FROM pg_create_logical_replication_slot('myslot',
'pgoutput', false, true, true);

~

I felt it is better to say "Ex1" / "Ex2" (or "1" / "2" or something
similar) to indicate better where these examples start and finish,
otherwise they just sort of get lost among the text.

======
doc/src/sgml/catalogs.sgml

3.
From previous review ([1] #6) I suggested reordering fields. Hous-san
wrote: "but I think the functionality of two fields are different and
I didn’t find the correlation between two fields except for the type
of value."

Yes, that is true. OTOH, I felt grouping the attributes by the same
types made the docs easier to read.

======
src/backend/commands/subscriptioncmds.c

4. CreateSubscription

+ /*
+ * If only the slot_name is specified (without create_slot option),
+ * it is possible that the user intends to use an existing slot on
+ * the publisher, so here we enable failover for the slot if
+ * requested.
+ */
+ else if (opts.slot_name && failover_enabled)
+ {

Unanswered question from previous review (see [1] #11a). i.e. How does
this condition ensure that *only* the slot name was specified (like
the comment is saying)?

~~~

5. AlterSubscription

  errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed
when two_phase is enabled"),
  errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh =
false, or with copy_data = false, or use DROP/CREATE
SUBSCRIPTION.")));

+ if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED && opts.copy_data)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed
when failover is enabled"),
+ errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh =
false, or with copy_data = false, or use DROP/CREATE
SUBSCRIPTION.")));
+

There are translations issues same as reported in my previous review
(see [1] #12b and also several other places as noted in [1]). Hou-san
replied that I "can start a separate thread to change the twophase
related messages, and we can change accordingly if it's accepted.",
but that's not right IMO because it is only the fact that this
sysncslot patch is reusing a similar message that warrants the need to
extract a "common" message part in the first place. So I think it is
responsibility if this sycslot patch to make this change.

======
src/backend/replication/logical/tablesync.c

6. process_syncing_tables_for_apply

+ if (MySubscription->twophasestate == LOGICALREP_TWOPHASE_STATE_PENDING)
+ ereport(LOG,
+ (errmsg("logical replication apply worker for subscription \"%s\"
will restart so that two_phase can be enabled",
+ MySubscription->name)));
+
+ if (MySubscription->failoverstate == LOGICALREP_FAILOVER_STATE_PENDING)
+ ereport(LOG,
+ (errmsg("logical replication apply worker for subscription \"%s\"
will restart so that failover can be enabled",
+ MySubscription->name)));

6a.
You may end up log 2 restart messages for the same restart. Is it OK?

~

6b.
This is another example where you should share the same common message
(for less translations)

======
src/backend/replication/logical/worker.c

7.
+ * The logical slot on the primary can be synced to the standby by specifying
+ * the failover = true when creating the subscription. Enabling failover allows
+ * us to smoothly transition to the standby in case the primary gets promoted,
+ * ensuring that we can subscribe to the new primary without losing any data.

/the failover = true/the failover = true option/

or

/the failover = true/failover = true/

~~~

8.
+
 #include "postgres.h"

Unnecessary extra blank line

======
src/backend/replication/slot.c

9. validate_standby_slots

There was no reply to the comment in my previous review (see [1] #27).
Maybe you disagree or maybe accidentally overlooked?

~~~

10. check_standby_slot_names

In previous review I asked ([1] #28) why a special check was needed
for "*". Hou-san replied that "SplitIdentifierString() does not give
error for '*' and '*' can be considered as valid value which if
accepted can mislead user".

Sure, but won't the code then just try to find if there is a
replication slot called "*" and that will fail. That was my point, if
the slot name lookup is going to fail anyway then why have the extra
code for the special "*" case up-front? Note -- I haven't tried it, so
maybe code doesn't work like I think it does.

======
src/backend/replication/walsender.c

11. PhysicalWakeupLogicalWalSnd

No reply to my previous review comment ([1] #33). Not done? Disagreed,
or accidentally missed?

~~~

12. WalSndFilterStandbySlots

+ /*
+ * If logical slot name is given in standby_slot_names, give WARNING
+ * and skip it. Since it is harmless, so WARNING should be enough, no
+ * need to error-out.
+ */
+ else if (SlotIsLogical(slot))
+ warningfmt = _("cannot have logical replication slot \"%s\" in
parameter \"%s\", ignoring");

I previously raised an issue (see [1] #35) thinking this could not
happen. Hou-san explained how it might happen ("user could drop the
logical slot and recreate a physical slot with the same name without
changing the GUC.") so this code was necessary. That is OK, but I
think your same explanation in the code commen.

~~~

13. WalSndFilterStandbySlots

+ standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);

I previously raised issue (see [1] #36). Hou-san replied "I think it's
OK to remove slots if it's invalidated, dropped, or was changed to
logical one as we don't need to wait for these slots to catch up
anymore."

Sure, maybe code is fine, but my point was that the code is removing
elements *more* scenarios than are mentioned by the function comment,
so maybe update that function comment for all the removal scenarios.

~~~

14. WalSndWaitForStandbyConfirmation

The comment change from my previous review ([1] #37) not done.
Disagreed, or accidentally missed?

~~~

15. WalSndWaitForStandbyConfirmation

The question about calling ConditionVariablePrepareToSleep in my
previous review ([1] #39) not answered. Accidentally missed?

~~~

16. WalSndWaitForWal

  if (RecentFlushPtr != InvalidXLogRecPtr &&
  loc <= RecentFlushPtr)
- return RecentFlushPtr;
+ {
+ WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);

It is better to use XLogRecPtrIsInvalid macro here. I know it was not
strictly added by your patch, but so much else changed nearby so I
thought this should be fixed at the same time.

======
src/bin/pg_upgrade/info.c

17. get_old_cluster_logical_slot_infos

+
  slotinfos = (LogicalSlotInfo *) pg_malloc(sizeof(LogicalSlotInfo) *
num_slots);

Excessive whitespace.

======
[1] My previous review of v35-0001.
https://www.postgresql.org/message-id/CAHut%2BPv-yu71ogj_hRi6cCtmD55bsyw7XTxj1Nq8yVFKpY3NDQ%40mail.g...

Kind Regards,
Peter Smith.
Fujitsu Australia






^ permalink  raw  reply  [nested|flat] 113+ messages in thread

* RE: Synchronizing slots from primary to standby
  2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
  2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
  2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
  2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
  2023-11-29 03:04           ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
@ 2023-11-29 09:12             ` Zhijie Hou (Fujitsu) <[email protected]>
  0 siblings, 0 replies; 113+ messages in thread

From: Zhijie Hou (Fujitsu) @ 2023-11-29 09:12 UTC (permalink / raw)
  To: Peter Smith <[email protected]>; +Cc: shveta malik <[email protected]>; Drouvot, Bertrand <[email protected]>; Amit Kapila <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; Bharath Rupireddy <[email protected]>; Peter Eisentraut <[email protected]>; Bruce Momjian <[email protected]>; Ashutosh Sharma <[email protected]>; Andres Freund <[email protected]>; pgsql-hackers; Ajin Cherian <[email protected]>; Alvaro Herrera <[email protected]>

On Wednesday, November 29, 2023 11:04 AM Peter Smith <[email protected]> wrote:


Thanks for the comments.

> ======
> 1. General.
> 
> Previously (see [1] #0) I asked a question about if there is some documentation
> missing. Seems not yet answered.

The document was add in V39-0002 in logicaldecoding.sgml
because some necessary GUCs for slotsync are not in 0001.

> 
> ======
> Commit message
> 
> 2.
> Users can set this flag during CREATE SUBSCRIPTION or during
> pg_create_logical_replication_slot API. Examples:
> 
> CREATE SUBSCRIPTION mysub CONNECTION '..' PUBLICATION mypub WITH
> (failover = true);
> 
> (failover is the last arg)
> SELECT * FROM pg_create_logical_replication_slot('myslot',
> 'pgoutput', false, true, true);
> 
> ~
> 
> I felt it is better to say "Ex1" / "Ex2" (or "1" / "2" or something
> similar) to indicate better where these examples start and finish, otherwise they
> just sort of get lost among the text.

Changed.

> 
> ======
> doc/src/sgml/catalogs.sgml
> 
> 3.
> From previous review ([1] #6) I suggested reordering fields. Hous-san
> wrote: "but I think the functionality of two fields are different and I didn’t find
> the correlation between two fields except for the type of value."
> 
> Yes, that is true. OTOH, I felt grouping the attributes by the same types made
> the docs easier to read.

The document's order should be same as the pg_subscription catalog, and I
prefer not to move the new subfailoverstate in the middle of catalog as the
functionality of them is different.

> 
> ======
> src/backend/commands/subscriptioncmds.c
> 
> 4. CreateSubscription
> 
> + /*
> + * If only the slot_name is specified (without create_slot option),
> + * it is possible that the user intends to use an existing slot on
> + * the publisher, so here we enable failover for the slot if
> + * requested.
> + */
> + else if (opts.slot_name && failover_enabled) {
> 
> Unanswered question from previous review (see [1] #11a). i.e. How does this
> condition ensure that *only* the slot name was specified (like the comment is
> saying)?

It is the else part of 'if (opts.create_slot)', so it means create_slot is not
specified while only slot_name is specified. I have improved the comment.


> 
> ~~~
> 
> 5. AlterSubscription
> 
>   errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed
> when two_phase is enabled"),
>   errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false,
> or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
> 
> + if (sub->failoverstate == LOGICALREP_FAILOVER_STATE_ENABLED &&
> + opts.copy_data) ereport(ERROR,
> + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> + errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed
> when failover is enabled"),
> + errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh =
> false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
> +
> 
> There are translations issues same as reported in my previous review (see [1]
> #12b and also several other places as noted in [1]). Hou-san replied that I "can
> start a separate thread to change the twophase related messages, and we can
> change accordingly if it's accepted.", but that's not right IMO because it is only
> the fact that this sysncslot patch is reusing a similar message that warrants the
> need to extract a "common" message part in the first place. So I think it is
> responsibility if this sycslot patch to make this change.

OK, changed.

> 
> ======
> src/backend/replication/logical/tablesync.c
> 
> 6. process_syncing_tables_for_apply
> 
> + if (MySubscription->twophasestate ==
> + LOGICALREP_TWOPHASE_STATE_PENDING)
> + ereport(LOG,
> + (errmsg("logical replication apply worker for subscription \"%s\"
> will restart so that two_phase can be enabled",
> + MySubscription->name)));
> +
> + if (MySubscription->failoverstate ==
> + LOGICALREP_FAILOVER_STATE_PENDING)
> + ereport(LOG,
> + (errmsg("logical replication apply worker for subscription \"%s\"
> will restart so that failover can be enabled",
> + MySubscription->name)));
> 
> 6a.
> You may end up log 2 restart messages for the same restart. Is it OK?

I think it's OK as it can provide complete information.

> 
> ~
> 
> 6b.
> This is another example where you should share the same common message
> (for less translations)

I adjusted the message there.

> 
> ======
> src/backend/replication/logical/worker.c
> 
> 7.
> + * The logical slot on the primary can be synced to the standby by
> + specifying
> + * the failover = true when creating the subscription. Enabling
> + failover allows
> + * us to smoothly transition to the standby in case the primary gets
> + promoted,
> + * ensuring that we can subscribe to the new primary without losing any data.
> 
> /the failover = true/the failover = true option/
> 
> or
> 
> /the failover = true/failover = true/
> 

Changed.

> ~~~
> 
> 8.
> +
>  #include "postgres.h"
> 
> Unnecessary extra blank line

Removed.

> 
> ======
> src/backend/replication/slot.c
> 
> 9. validate_standby_slots
> 
> There was no reply to the comment in my previous review (see [1] #27).
> Maybe you disagree or maybe accidentally overlooked?

The error message has already been adjusted in V39.
I adjusted the check in this version as well to be consistent.

> 
> ~~~
> 
> 10. check_standby_slot_names
> 
> In previous review I asked ([1] #28) why a special check was needed for "*".
> Hou-san replied that "SplitIdentifierString() does not give error for '*' and '*'
> can be considered as valid value which if accepted can mislead user".
> 
> Sure, but won't the code then just try to find if there is a replication slot called
> "*" and that will fail. That was my point, if the slot name lookup is going to fail
> anyway then why have the extra code for the special "*" case up-front? Note --
> I haven't tried it, so maybe code doesn't work like I think it does.

I think allowing "*" can mislead user because it normally means every slot, but
we don't want to support the "every slot" option as mentioned in the comment.
So I think reject it here is fine. Reporting ERROR because the slot named '*' was not
there may look confusing.

> 
> ======
> src/backend/replication/walsender.c
> 
> 11. PhysicalWakeupLogicalWalSnd
> 
> No reply to my previous review comment ([1] #33). Not done? Disagreed, or
> accidentally missed?

The function mentioned in your previous comment has been removed in
previous version, so I am not sure are you pointing to some other codes
that has similar issues ?

> 
> ~~~
> 
> 12. WalSndFilterStandbySlots
> 
> + /*
> + * If logical slot name is given in standby_slot_names, give WARNING
> + * and skip it. Since it is harmless, so WARNING should be enough, no
> + * need to error-out.
> + */
> + else if (SlotIsLogical(slot))
> + warningfmt = _("cannot have logical replication slot \"%s\" in
> parameter \"%s\", ignoring");
> 
> I previously raised an issue (see [1] #35) thinking this could not happen.
> Hou-san explained how it might happen ("user could drop the logical slot and
> recreate a physical slot with the same name without changing the GUC.") so this
> code was necessary. That is OK, but I think your same explanation in the code
> commen.

OK, I have added comments here.

> 
> ~~~
> 
> 13. WalSndFilterStandbySlots
> 
> + standby_slots_cpy = foreach_delete_current(standby_slots_cpy, lc);
> 
> I previously raised issue (see [1] #36). Hou-san replied "I think it's OK to remove
> slots if it's invalidated, dropped, or was changed to logical one as we don't
> need to wait for these slots to catch up anymore."
> 
> Sure, maybe code is fine, but my point was that the code is removing elements
> *more* scenarios than are mentioned by the function comment, so maybe
> update that function comment for all the removal scenarios.

Updated the comments.

> 
> ~~~
> 
> 14. WalSndWaitForStandbyConfirmation
> 
> The comment change from my previous review ([1] #37) not done.
> Disagreed, or accidentally missed?

Thanks for pointing, this was missed.

> 
> ~~~
> 
> 15. WalSndWaitForStandbyConfirmation
> 
> The question about calling ConditionVariablePrepareToSleep in my previous
> review ([1] #39) not answered. Accidentally missed?

I think V39 has already adjusted the order of reload and NIL check in this function.

> 
> ~~~
> 
> 16. WalSndWaitForWal
> 
>   if (RecentFlushPtr != InvalidXLogRecPtr &&
>   loc <= RecentFlushPtr)
> - return RecentFlushPtr;
> + {
> + WalSndFilterStandbySlots(RecentFlushPtr, &standby_slots);
> 
> It is better to use XLogRecPtrIsInvalid macro here. I know it was not strictly
> added by your patch, but so much else changed nearby so I thought this should
> be fixed at the same time.

Changed.

> 
> ======
> src/bin/pg_upgrade/info.c
> 
> 17. get_old_cluster_logical_slot_infos
> 
> +
>   slotinfos = (LogicalSlotInfo *) pg_malloc(sizeof(LogicalSlotInfo) * num_slots);
> 
> Excessive whitespace.

Removed.

Best Regards,
Hou zj



^ permalink  raw  reply  [nested|flat] 113+ messages in thread


end of thread, other threads:[~2023-12-21 09:36 UTC | newest]

Thread overview: 113+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-03-19 01:54 [PATCH 5/7] fix Tomas Vondra <[email protected]>
2020-03-19 02:02 [PATCH 7/7] fix Tomas Vondra <[email protected]>
2020-03-19 15:55 [PATCH 4/8] fix Tomas Vondra <[email protected]>
2020-03-19 17:26 [PATCH 6/8] fix Tomas Vondra <[email protected]>
2020-03-19 17:48 [PATCH 8/8] fix Tomas Vondra <[email protected]>
2022-12-14 09:26 Allow tailoring of ICU locales with custom rules Peter Eisentraut <[email protected]>
2023-11-21 05:39 Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
2023-11-27 04:02 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-11-27 06:02   ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-11-27 08:45     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-11-27 08:50       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-11-27 08:57         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-11-27 10:38           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-11-28 03:13             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-11-28 06:49               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-11-28 09:40                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-11-28 09:44                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-11-28 15:58                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-11-29 04:17                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-11-29 09:11                     ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-11-30 07:05                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
2023-12-01 04:10                       ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-12-01 04:51                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-01 05:47                           ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-12-01 07:17                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-01 09:33                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-11-28 12:06           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-11-29 05:58             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-11-29 09:54               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-12-01 11:06                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-01 11:15                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-12-01 12:10                     ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
2023-12-01 16:00                       ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
2023-12-02 04:19                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-04 05:10                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-04 05:15                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-04 16:36                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-12-05 05:08                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-05 08:48                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-12-05 10:29                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-05 13:38                                 ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-12-05 11:32                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-05 14:08                               ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-12-06 05:26                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-06 06:18                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-06 09:30                                     ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-12-06 10:58                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-06 11:23                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-07 07:53                                           ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-12-07 08:03                                           ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
2023-12-11 09:30                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-18 12:11                                               ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-12-07 11:36                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-08 12:05                                             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-12-11 00:16                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
2023-12-11 02:19                                                 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-12-11 07:32                                                   ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
2023-12-11 08:35                                                     ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-12 09:47                                                     ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-12-12 12:26                                                       ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
2023-12-13 06:09                                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-07 22:58                                           ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
2023-12-08 09:06                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-11 07:52                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-12-11 11:43                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-13 05:10                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-13 06:12                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-13 11:37                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-14 01:30                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
2023-12-14 11:10                                                         ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-15 05:32                                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-15 05:33                                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-18 10:52                                                               ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-19 11:47                                                                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-19 13:19                                                                   ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-18 23:20                                                               ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
2023-12-19 06:06                                                                 ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-19 11:40                                                                   ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-19 13:05                                                                     ` RE: Synchronizing slots from primary to standby Hayato Kuroda (Fujitsu) <[email protected]>
2023-12-20 05:52                                                                       ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-20 08:03                                                                         ` RE: Synchronizing slots from primary to standby Hayato Kuroda (Fujitsu) <[email protected]>
2023-12-20 12:42                                                                           ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-12-21 02:23                                                                             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-12-21 04:24                                                                             ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
2023-12-20 12:43                                                                       ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-12-20 06:31                                                                     ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
2023-12-21 09:36                                                                       ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-14 04:45                                                       ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
2023-12-14 05:53                                                         ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-12-15 05:42                                                         ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-15 12:25                                                           ` Re: Synchronizing slots from primary to standby Nisha Moond <[email protected]>
2023-12-17 02:51                                                             ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-12 09:46                                               ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-12-11 08:17                                             ` Re: Synchronizing slots from primary to standby Dilip Kumar <[email protected]>
2023-12-11 08:50                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-11 09:02                                                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-13 10:22                                                   ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
2023-12-14 05:38                                                     ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-15 05:39                                                     ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-11 11:17                                                 ` Re: Synchronizing slots from primary to standby Dilip Kumar <[email protected]>
2023-12-10 11:03                                           ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-11 09:11                                             ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-11 13:42                                               ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-12 09:14                                                 ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-13 03:31                                                   ` Re: Synchronizing slots from primary to standby Amit Kapila <[email protected]>
2023-12-07 07:49                                         ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-12-07 09:07                                           ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-07 09:27                                             ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-12-08 03:03                                               ` Re: Synchronizing slots from primary to standby shveta malik <[email protected]>
2023-12-04 03:33                 ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>
2023-12-04 14:37                   ` Re: Synchronizing slots from primary to standby Drouvot, Bertrand <[email protected]>
2023-11-29 03:04           ` Re: Synchronizing slots from primary to standby Peter Smith <[email protected]>
2023-11-29 09:12             ` RE: Synchronizing slots from primary to standby Zhijie Hou (Fujitsu) <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox